@crimson-education/browser-logger
v5.0.11
Published
An abstract logger and reporting utility for browser environments
Readme
Crimson Education Browser Logger
A structured logger and analytics reporting utility for browser environments.
This library contains a number of reporters that is will send analytics to.
These must be enabled by calling Logger.init(), as seen below.
Note: This library has been upgraded to support Node.js 22. Some features have been removed or modified during the upgrade process. See the Upgrade Notes section for details.
!!IMPORTANT NOTICE: for the legacy lib of Datadog V4.X and others, please see the main branch, this is the one upgraded.
Install
npm i @crimson-education/browser-loggeror
yarn add @crimson-education/browser-loggerFor new frontend applications, including Replit-hosted applications, follow the frontend integration guide. It defines the recommended split between Datadog Logs, PostHog product analytics, and allowlisted session replay.
Usage
Initialize
import * as Logger from '@crimson-education/browser-logger';
import { config } from '../configuration';
Logger.init({
service: 'test-project',
application: 'crimson-app',
environment: config.environment,
version: config.version,
defaultMetadata: {
domain: window.location.hostname,
},
reporters: {
log: true,
gtm: true,
datadog: {
applicationId: config.datadogApplicationId,
clientToken: config.datadogClientToken,
site: config.datadogSite,
proxyUrl: config.datadogTunnelProxyUrl,
version: config.datadogVersion,
sampleRate: config.environment === 'production' ? 50 : 0,
replaySampleRate: config.environment === 'production' ? 50 : 0,
forwardConsoleLogs: true,
logTransport: {
level: Logger.LogLevel.Info,
},
trackUserInteractions: true,
allowedTrackingOrigins: ['https://my.api.domain'],
},
posthog: {
apiKey: config.posthogApiKey,
apiHost: config.posthogApiHost,
capturePageview: 'history_change',
replaySampleRate: 0,
},
},
});
Logger.setUser({
id: '123',
email: '[email protected]',
name: 'Billy Brown',
});
// Once the user is authenticated.
Logger.recordSession();
Logger.trackEvent({
name: 'App Loaded',
metadata: {
type: 'SPA',
},
});
export const logger = Logger.createLogger();
// Attributed logging.
logger.info('WOW!', {
id: 1,
});Structured Logging
Structured logging in this library is loosely based on Winston, and follows roughly the same interface as @crimson-education/node-logger.
Call Logger.createLogger() to create a logger instance, it is recommended to re-use this as a global logger everywhere, and then call .child() to reuse metadata.
Global metadata about the service will be added for transport usage after calling Logger.init().
You can add more Global Metadata at any time by calling addMetadata().
export const logger = Logger.createLogger();
const log = logger.child({
from: 'childLog',
});
log.info('My Log', { jobCount: jobs.length });
const timer = log.startTimer();
const result = await job(jobs);
timer.done({ message: 'Job Completed', metadata: { id: result.id } });Reporting
Opting out of events
You can opt out of sending events to reporters on a global, or per call basis using the endpoints config.
E.g. On calls
Logger.trackEvent({
message: 'my-event',
toReporters: ['log', 'datadog'],
excludeReporters: ['datadog'],
});E.g. Globally
Logger.init({
reporters: {
log: true,
datadog: {
...
endpoints: {
trackEvent: false,
}
}
}
});These will only send to the log reporter, it is recommended to use toReporters or excludeReporters separately as they overlap.
Filtering out metadata
You can filter what metadata gets sent to each reporter in the reporter config.
This accepts a string for exact match metadata keys, or a RegExp.
You can use . to traverse metadata.
Logger.init({
reporters: {
log: {
ignoreMetadataPatterns: ['internalError', 'user.email', /error\.stack.*/g],
},
},
});Filtering out Breadcrumb categories
You can filter what breadcrumbs get sent to reports in the reporter config.
Logger.init({
reporters: {
log: {
ignoreBreadcrumbCategories: [
'fetch'
]
}
}
});
// Not sent to the log reporter.
Logger.addBreadcrumb({
...
category: 'fetch',
});Configure Reporters
Proxies
Some reporters support taking a proxyUrl parameter in their config. When provided, the reporter will send events to the proxyUrl instead of the default destination. This can be useful to get around ad blockers users might have installed. Typically, proxyUrl should point to our internal proxy service--a shared service that forwards requests it receives to a third-party system.
Our internal proxy service is defined in our crimson-infrastructure repository.
Log
By default, the log reporter is enabled. This adds all reporter functions to logs. Set reporters.log to false to disable reporter logging.
import { config } from '../configuration';
import * as Logger from '@crimson-education/browser-logger';
Logger.init({
reporters: {
log: {
trackEventLevel: Logger.LogInfo.Debug,
endpoints: {
recordSession: false,
recordSessionStop: false,
},
},
},
});Check out the DatadogReporterConfig for all of the configuration you can apply to Datadog reporting.
Datadog
To configure Datadog, this requires at least applicationId, clientToken and site (As we use an EU instance).
import { config } from '../configuration';
import * as Logger from '@crimson-education/browser-logger';
Logger.init({
reporters: {
datadog: {
applicationId: config.datadogApplicationId,
clientToken: config.datadogClientToken,
site: config.datadogSite,
proxyUrl: config.datadogTunnelProxyUrl,
version: config.datadogVersion,
sampleRate: config.environment === 'production' ? 50 : 0,
replaySampleRate: config.environment === 'production' ? 50 : 0,
forwardConsoleLogs: true,
logTransport: {
level: Logger.LogLevel.Info,
},
trackUserInteractions: true,
allowedTrackingOrigins: ['https://my.api.domain'],
},
},
});Check out the DatadogReporterConfig for all of the configuration you can apply to Datadog reporting.
This automatically adds a Datadog Log Transport that transmits Log data to Datadog, this can be customized in DatadogReporterConfig with logTransport, or disabled by setting logTransport to false.
If you need to keep browser logs while disabling RUM, or vice versa, use the independent Datadog switches:
Logger.init({
reporters: {
datadog: {
applicationId: config.datadogApplicationId,
clientToken: config.datadogClientToken,
site: config.datadogSite,
logsEnabled: true,
rumEnabled: false,
logTransport: {
level: Logger.LogLevel.Info,
},
},
},
});logsEnabled only affects Datadog Browser Logs and the Datadog log transport. rumEnabled only affects Datadog Browser
RUM, browser actions, view tracking, error reporting, and replay controls.
PostHog
To configure PostHog, this requires at least apiKey and apiHost.
import { config } from '../configuration';
import * as Logger from '@crimson-education/browser-logger';
Logger.init({
reporters: {
posthog: {
apiKey: config.posthogApiKey,
apiHost: config.posthogApiHost,
capturePageview: 'history_change',
replaySampleRate: 0,
trackViewsManually: false,
}
}
});replaySampleRate is provided as a compatibility alias for existing Datadog-style config. Values less than or equal to 0 disable session recording at startup, while Logger.recordSession() can still enable replay later for allowlisted users.
For an authenticated SPA whose initial Page View must carry the final user identity, set
captureInitialPageviewOnIdentify: true. The reporter drops automatic Page Views until the first valid
Logger.setUser(...), then emits the current page once with that identity. Keep normal
capturePageview: 'history_change' enabled so subsequent navigation remains automatic. Do not enable this
for public or anonymous flows where pre-login views are intentionally retained.
When multiple repos write into the same PostHog project, prefer setting application at Logger.init(...). This ensures a stable product-level dimension is present on all events independently of each app's service name.
Person Profile processing defaults to never. Authenticated user fields are still registered on events, but PostHog
does not create billable Person Profiles. Applications that require PostHog cohorts, lifecycle analysis, or
person-property targeting must explicitly set personProfiles: 'identified_only' and account for identified-event
pricing.
In anonymous mode, the reporter continuously enforces anonymous event delivery. It clears identity state left by an
earlier identified setup whenever that state is detected, drops person mutation events, removes person-property
updates, and applies anonymous billing flags after consumer beforeSend hooks. A cleanup changes the PostHog distinct
ID and session boundary, but stable user_id, user_email, user_name, and user_role event properties remain
available for warehouse reporting. migratePersistedIdentifiedState: false disables the identity-state reset only;
events are still sent without person processing while personProfiles is never.
Migration uses explicit identified-state markers, not inequality between device and distinct IDs: PostHog preserves the former and rotates the latter on an anonymous reset. Repeated normal events must not reset the session or restart replay. Explicit logout still resets identity; the existing replay opt-in policy and anonymous event-billing enforcement are unchanged.
Filtering technical analytics noise
Logger.trackEvent() still fans out to every enabled reporter. If a repo wants to keep technical log noise in the log
reporter while excluding it from Datadog RUM or PostHog, configure ignoreTrackEventPatterns explicitly in that
reporter.
Example:
Logger.init({
reporters: {
datadog: {
applicationId: config.datadogApplicationId,
clientToken: config.datadogClientToken,
site: config.datadogSite,
ignoreTrackEventPatterns: [
'TIM_LOGIN_SUCCESS',
/^TIM_MESSAGE_RECEIVED:/,
/^feedback fetch (triggered|deduped inflight|skipped ttl|success)$/,
],
},
posthog: {
apiKey: config.posthogApiKey,
apiHost: config.posthogApiHost,
ignoreTrackEventPatterns: [
'TIM_LOGIN_SUCCESS',
/^TIM_MESSAGE_RECEIVED:/,
],
},
},
});Google Tag Manager
To configure GTM, you will need to have loaded the GTM script into your App. See: https://support.google.com/tagmanager/answer/6103696?hl=en
import { config } from '../configuration';
import * as Logger from '@crimson-education/browser-logger';
// No configuration
Logger.init({
reporters: {
gtm: true,
},
});
// With configuration
Logger.init({
reporters: {
gtm: {
ignoreBreadcrumbCategories: ['fetch'],
},
},
});Check out the GTMReporterConfig for all of the configuration you can apply to Google Tag Manager reporting.
Functions
See the src/logger/index.ts file for all exported functions of the Logger. See the src/reporter/index.ts file for all exported functions of the Reporter.
Improve Session Tracking with Component names
You can improve the name of components in analytics tools like Datadog, instead of using the content of a component. This is valuable if the content is dynamic, e.g. a user's name:
<div data-analytics-name="MyUserName">Billy Brown</div>This will result in the session saying something like, clicked on "MyUserName".
Upgrade Notes
Node.js 22 Upgrade Changes
This library has been upgraded to support Node.js 22. The following changes were made during the upgrade process:
Datadog Reporter Changes
trackInteractions→trackUserInteractions(renamed for clarity)trackFrustrations→ Removed (replaced with custom frustration detection implementation)allowedTracingOrigins→allowedTrackingOrigins(corrected spelling)
Removed Features
The following features were removed during the upgrade and have been replaced with custom implementations:
Automatic Frustration Detection: Replaced with custom implementation that detects:
- Rapid clicks (potential frustration)
- Form validation errors
- 404 and network errors
Enhanced Auto-tracking: Replaced with custom implementation that tracks:
- Page views and route changes
- User interactions with analytics-enabled elements
- Session state changes (active/inactive)
Configuration Updates Required
If you're upgrading from a previous version, update your configuration:
// Old configuration
datadog: {
trackInteractions: true,
trackFrustrations: true,
allowedTracingOrigins: ['https://my.api.domain']
}
// New configuration
datadog: {
trackUserInteractions: true,
allowedTrackingOrigins: ['https://my.api.domain']
// trackFrustrations is now handled automatically
}For more details about the upgrade process and custom implementations, see the individual reporter configuration files.
