Examples
This page contains practical examples showing how to use RN Structured Logger in real-world scenarios.
Basic Setup Examples
Development vs Production Configuration
import { initLogger, ConsoleTransport, FileTransport, SentryTransport, makeRedactor } from 'rn-structured-logger';
import { Platform } from 'react-native';
// Development configuration
const devConfig = {
level: 'debug',
transports: [ConsoleTransport],
patchConsole: true,
};
// Production configuration
const prodConfig = {
level: 'info',
transports: [
ConsoleTransport,
// File logging only on native platforms
...(Platform.OS !== 'web' ? [FileTransport({ fileName: 'app.log' })] : []),
SentryTransport(),
],
redactor: makeRedactor(['password', 'token', 'email', 'ssn']),
sampling: { rate: 0.1 }, // Sample 10% of non-error logs
rateLimit: { maxPerMin: 300 },
batch: { size: 50, intervalMs: 2000 },
};
// Initialize based on environment
initLogger(__DEV__ ? devConfig : prodConfig);
Expo App with Conditional File Logging
import { initLogger, ConsoleTransport, FileTransport, makeRedactor } from 'rn-structured-logger';
import * as Device from 'expo-device';
import { Platform } from 'react-native';
// Gather device information
const deviceInfo = {
platform: Platform.OS,
appVersion: '1.0.0',
deviceModel: Device.modelName || 'unknown',
osVersion: Device.osVersion || 'unknown',
deviceName: Device.deviceName || 'unknown',
};
// Initialize logger with platform-specific transports
initLogger({
level: 'info',
// Use file transport only on native platforms
transports: Platform.OS === 'web'
? [ConsoleTransport]
: [ConsoleTransport, FileTransport({ fileName: 'expo-app.log' })],
redactor: makeRedactor(['password', 'token', 'email']),
device: deviceInfo,
patchConsole: true,
});
Logging Patterns
User Authentication Flow
import { getLogger } from 'rn-structured-logger';
const authLogger = getLogger('auth');
const apiLogger = getLogger('api');
class AuthService {
async login(credentials: { email: string; password: string }) {
const correlationId = `login-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
// Set correlation ID for the entire flow
authLogger.setCorrelationId(correlationId);
try {
authLogger.info('Login attempt started', {
email: credentials.email,
loginMethod: 'email'
});
// Validate credentials
authLogger.debug('Validating credentials');
// Make API call
apiLogger.info('Calling authentication API', {
endpoint: '/auth/login',
method: 'POST'
});
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: credentials.email,
// Password is automatically redacted by our redactor
password: credentials.password
})
});
if (!response.ok) {
throw new Error(`Login failed: ${response.status}`);
}
const userData = await response.json();
authLogger.info('Login successful', {
userId: userData.id,
userRole: userData.role
});
return userData;
} catch (error) {
authLogger.error('Login failed', {
error: error.message,
email: credentials.email
});
throw error;
} finally {
// Clear correlation ID
authLogger.setCorrelationId(undefined);
}
}
}
API Request/Response Logging
import { getLogger } from 'rn-structured-logger';
const apiLogger = getLogger('api');
const errorLogger = getLogger('error');
class ApiClient {
private async makeRequest<T>(
method: string,
url: string,
options: RequestInit = {}
): Promise<T> {
const requestId = `req-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
const startTime = Date.now();
apiLogger.info('API request started', {
requestId,
method,
url,
headers: options.headers,
bodySize: options.body ? JSON.stringify(options.body).length : 0
});
try {
const response = await fetch(url, {
method,
...options,
headers: {
'X-Request-ID': requestId,
...options.headers
}
});
const responseTime = Date.now() - startTime;
if (!response.ok) {
const errorText = await response.text();
apiLogger.warn('API request failed', {
requestId,
status: response.status,
statusText: response.statusText,
responseTime,
error: errorText
});
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
apiLogger.info('API request completed', {
requestId,
status: response.status,
responseTime,
dataSize: JSON.stringify(data).length
});
return data;
} catch (error) {
const responseTime = Date.now() - startTime;
errorLogger.error('API request error', {
requestId,
method,
url,
responseTime,
error: error.message,
stack: error.stack
});
throw error;
}
}
async get<T>(url: string, options?: RequestInit): Promise<T> {
return this.makeRequest('GET', url, options);
}
async post<T>(url: string, data: any, options?: RequestInit): Promise<T> {
return this.makeRequest('POST', url, {
...options,
headers: {
'Content-Type': 'application/json',
...options?.headers
},
body: JSON.stringify(data)
});
}
}
Error Boundary with Structured Logging
import React, { Component, ErrorInfo, ReactNode } from 'react';
import { getLogger } from 'rn-structured-logger';
const errorLogger = getLogger('error');
const uiLogger = getLogger('ui');
interface Props {
children: ReactNode;
fallback?: ReactNode;
}
interface State {
hasError: boolean;
error?: Error;
}
class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
// Log the error with full context
errorLogger.error('React Error Boundary caught an error', {
error: error.message,
stack: error.stack,
componentStack: errorInfo.componentStack,
errorBoundary: 'main',
timestamp: new Date().toISOString()
});
// Also log UI-specific information
uiLogger.fatal('Application error occurred', {
errorType: 'react_error_boundary',
errorMessage: error.message,
hasFallback: !!this.props.fallback
});
}
render() {
if (this.state.hasError) {
return this.props.fallback || (
<div style={{ padding: 20, textAlign: 'center' }}>
<h2>Something went wrong</h2>
<p>The error has been logged and reported.</p>
<button onClick={() => this.setState({ hasError: false })}>
Try again
</button>
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;
Performance Monitoring
import { getLogger } from 'rn-structured-logger';
const perfLogger = getLogger('performance');
const apiLogger = getLogger('api');
class PerformanceMonitor {
private startTimes = new Map<string, number>();
start(operation: string, context?: any) {
const id = `${operation}-${Date.now()}`;
this.startTimes.set(id, Date.now());
perfLogger.debug('Operation started', {
operation,
operationId: id,
context
});
return id;
}
end(operationId: string, context?: any) {
const startTime = this.startTimes.get(operationId);
if (!startTime) {
perfLogger.warn('Performance monitoring: operation not found', {
operationId
});
return;
}
const duration = Date.now() - startTime;
this.startTimes.delete(operationId);
const level = duration > 5000 ? 'warn' : duration > 1000 ? 'info' : 'debug';
perfLogger.log(level, 'Operation completed', {
operationId,
duration,
context
});
// Log slow operations
if (duration > 5000) {
perfLogger.warn('Slow operation detected', {
operationId,
duration,
threshold: 5000,
context
});
}
}
async measureAsync<T>(
operation: string,
fn: () => Promise<T>,
context?: any
): Promise<T> {
const operationId = this.start(operation, context);
try {
const result = await fn();
this.end(operationId, { ...context, success: true });
return result;
} catch (error) {
this.end(operationId, { ...context, success: false, error: error.message });
throw error;
}
}
}
// Usage example
const perfMonitor = new PerformanceMonitor();
async function fetchUserData(userId: string) {
return perfMonitor.measureAsync(
'fetchUserData',
async () => {
const response = await fetch(`/api/users/${userId}`);
return response.json();
},
{ userId, endpoint: '/api/users/:id' }
);
}
Custom Transport Example
import { Transport, LogRecord } from 'rn-structured-logger';
class AnalyticsTransport implements Transport {
name = 'analytics';
private events: LogRecord[] = [];
write(records: LogRecord[]): void {
// Filter only info and warning levels for analytics
const analyticsEvents = records.filter(
record => record.level === 'info' || record.level === 'warn'
);
this.events.push(...analyticsEvents);
// Send to analytics service in batches
if (this.events.length >= 10) {
this.flush();
}
}
async flush(): Promise<void> {
if (this.events.length === 0) return;
try {
await fetch('/api/analytics/events', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
events: this.events.map(event => ({
name: `${event.ns || 'app'}_${event.level}`,
properties: {
message: event.msg,
context: event.ctx,
timestamp: event.ts,
correlationId: event.correlationId
}
}))
})
});
this.events = [];
} catch (error) {
console.error('Failed to send analytics events:', error);
// Keep events for retry
}
}
async dispose(): Promise<void> {
await this.flush();
}
}
// Usage
import { initLogger, ConsoleTransport } from 'rn-structured-logger';
initLogger({
level: 'info',
transports: [ConsoleTransport, new AnalyticsTransport()]
});
Configuration Examples
Environment-Based Configuration
// config/logger.config.ts
import { LoggerConfig, ConsoleTransport, FileTransport, SentryTransport, makeRedactor } from 'rn-structured-logger';
export function createLoggerConfig(): LoggerConfig {
const isDevelopment = __DEV__;
const isWeb = Platform.OS === 'web';
const baseConfig: LoggerConfig = {
level: isDevelopment ? 'debug' : 'info',
redactor: makeRedactor(['password', 'token', 'email', 'ssn', 'creditCard']),
patchConsole: isDevelopment,
};
if (isWeb) {
// Web configuration
return {
...baseConfig,
transports: [ConsoleTransport],
sampling: { rate: 0.5 }, // Sample 50% in web environment
};
} else {
// Native configuration
return {
...baseConfig,
transports: [
ConsoleTransport,
FileTransport({
fileName: `app-${new Date().toISOString().split('T')[0]}.log`,
maxBytes: 2 * 1024 * 1024, // 2MB
}),
...(isDevelopment ? [] : [SentryTransport()]),
],
sampling: { rate: isDevelopment ? 1 : 0.1 },
rateLimit: { maxPerMin: isDevelopment ? 1000 : 300 },
batch: { size: 25, intervalMs: 1000 },
};
}
}
React Native Navigation Logging
import { NavigationContainer } from '@react-navigation/native';
import { getLogger } from 'rn-structured-logger';
const navLogger = getLogger('navigation');
const navigationRef = React.createRef();
function App() {
return (
<NavigationContainer
ref={navigationRef}
onStateChange={(state) => {
if (state) {
const currentRoute = state.routes[state.index];
navLogger.info('Navigation state changed', {
currentRoute: currentRoute.name,
routeParams: currentRoute.params,
routeCount: state.routes.length,
navigationType: 'state_change'
});
}
}}
onReady={() => {
navLogger.info('Navigation container ready');
}}
>
{/* Your navigation structure */}
</NavigationContainer>
);
}
// Manual navigation logging
export function logNavigation(routeName: string, params?: any) {
navLogger.debug('Manual navigation', {
routeName,
params,
navigationType: 'manual'
});
}