-
Notifications
You must be signed in to change notification settings - Fork 31
Getting Started with the Node LWR Logger
Starting with RFLIB npm 11.0.0, the rflib npm package ships a server-side logger for Node.js applications that report into Salesforce using the same buffer-and-publish model as RFLIB's Apex and LWC loggers. Log messages are buffered per request/user and, once a configurable threshold is reached, the buffered stack is published as an rflib_Log_Event__e Platform Event β so it surfaces in the Ops Center dashboard and any configured log forwarders (Slack, Teams, Google Chat, App Insights, OpenTelemetry, β¦) alongside your on-platform logs.
Its primary documented host is Lightning Web Runtime (LWR) on Node.js, but the logger is runtime-agnostic: it works in any Node.js process (Express, AWS Lambda, a plain script) because the host supplies the Salesforce connection β the logger never authenticates on its own.
Replaces the Salesforce Functions logger. Salesforce Functions reached end of life on 31 January 2025. RFLIB npm 10.3 is the last line that targets Functions (it reads the now-removed
Functions_*Logger Settings fields). From RFLIB npm 11.0.0 the logger reads the standardClient_*Logger Settings and takes an injected data adapter, as described below. See Getting Started with Salesforce Functions for the legacy integration.
npm install rflib
# the reference adapter below uses jsforce to reach the org
npm install jsforcecreateLogger(dataApi, context, loggerName, options) returns a logger instance that owns its own in-memory log stack and configuration.
-
One logger per request/user. Because a Node/LWR server is long-running and serves many users concurrently, you must create a logger per request (or per authenticated user) and must not reuse a single instance across users. Each instance keeps its own stack, so one user's messages can never leak into another user's published Log Event β matching RFLIB's per-context convention.
-
Injected data adapter.
dataApiis any object exposingquery(soql)(returns the matching records) andcreate({ type, fields })(publishes a Platform Event). The logger reads its settings viaqueryand publishes viacreate. You typically adapt a jsforceConnection(see below). -
Configuration comes from
rflib_Logger_Settings__c(a Hierarchy custom setting), reusing the existing client-logging fields. The logger resolves the hierarchy the way ApexgetInstance()does β the most specific row wins β using the ids on the context: passcontext.user.id(and optionallycontext.user.profileId) to honor user/profile-level overrides; with onlycontext.org.idit uses org defaults.Logger config Logger Settings field computeLogLevelClient_Console_Log_Level__cserverLogLevelClient_Server_Log_Level__cstackSizeClient_Log_Size__ccomputeLogLevelcontrols what is echoed to the compute log (stdout /console);serverLogLevelcontrols when the buffered stack is published as a Platform Event (minimum effective levelINFO). -
Platform info. Each published event includes a
Platform_Info__cpayload. By default the logger collects Node runtime telemetry (process memory, cpu, uptime, version) under anodekey, which the Apexrflib_PlatformInfoTransformerflattens torflib.platform.node.*. Provideoptions.platformInfoProviderto override or extend it (e.g. add the LWR route, SSR flag, or request duration).
Authentication stays out of the logger. The recommended pattern for a server is the OAuth 2.0 JWT Bearer flow with a single integration user and a Connected App, so log events are published with a stable identity regardless of which end user triggered the request.
const jsforce = require('jsforce');
// Authenticate once per process and reuse the connection.
async function createConnection() {
const conn = new jsforce.Connection({ loginUrl: process.env.SF_LOGIN_URL });
await conn.authorize({
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
assertion: buildSignedJwt() // sign with your Connected App's certificate
});
return conn;
}
// Adapt a jsforce Connection to the minimal { query, create } shape the logger expects.
// Return the full `records` array so the logger can resolve the Logger Settings hierarchy
// (org / profile / user) β returning a single record limits it to org-level defaults.
function toDataApi(conn) {
return {
query: async (soql) => {
const result = await conn.query(soql);
return result.records;
},
create: ({ type, fields }) => conn.sobject(type).create(fields)
};
}The identity used by the connection publishes the Platform Events, so it needs the same access the Functions permission set used to provide:
-
Createpermission on therflib_Log_Event__ePlatform Event -
Createpermission on therflib_Application_Event_Occurred_Event__ePlatform Event -
Readaccess to therflib_Logger_Settings__cCustom Setting
Create the logger inside the per-request server hook, keyed to the authenticated user, so the stack is isolated per request.
const { createLogger } = require('rflib');
export async function getServerData() {
const conn = await getConnectionForThisProcess();
const user = this.request.user; // however your LWR app resolves the authenticated user
const logger = createLogger(
toDataApi(conn),
{
id: this.request.id, // recorded as Request_Id__c
org: { id: user.organizationId }, // org-default Logger Settings row
user: { id: user.userId, profileId: user.profileId } // resolves user/profile-level overrides
},
'home-route'
);
try {
logger.info('Rendering home route for user {0}', user.userId);
const data = await loadHomeData(conn);
return { data };
} catch (err) {
logger.fatal('Failed to render home route: {0}', err.message);
throw err;
}
}The same pattern works in an Express handler or a Lambda β create one logger per invocation/request.
createApplicationEventLogger(dataApi, context, options) publishes rflib_Application_Event_Occurred_Event__e events for business-level auditing:
const { createApplicationEventLogger } = require('rflib');
const appEvents = createApplicationEventLogger(toDataApi(conn), context);
appEvents.logApplicationEvent('checkout-completed', orderId, { total: 49.99 });createLogger(dataApi, context, loggerName, options) accepts an optional options object:
| Option | Default | Description |
|---|---|---|
computeLogger |
console |
Console-like sink for the compute (stdout) stream. |
shouldClearLogs |
false |
Clears this instance's log stack on creation. |
platformInfoProvider |
Node runtime telemetry | Returns the object stored in Platform_Info__c when an event publishes. |
| Functions (npm β€ 10.3) | Node / LWR (npm β₯ 11.0) |
|---|---|
createLogger(context, computeLogger, name) |
createLogger(dataApi, context, name, options) β the data adapter is now injected. |
Settings: Functions_Log_Size__c, Functions_Compute_Log_Level__c, Functions_Server_Log_Level__c
|
Settings: Client_Log_Size__c, Client_Console_Log_Level__c, Client_Server_Log_Level__c
|
| Salesforce supplied the connection/identity (Functions runtime) | The host supplies the connection (e.g. jsforce + JWT integration user). |
| One shared log stack across concurrent functions | One isolated log stack per logger instance (create one per request/user). |
-
Logger Settings β the
Client_*fields the logger reads - Getting Started with Logging β the on-platform logging model
- Getting Started with Salesforce Functions β the EOL Functions integration
- Home - Start here for installation and learning path
- Getting Started with Logging - Start here
- Logger Settings - Configure your logging
- Getting Started with Feature Switches - Control features
- Getting Started with Application Events - Track business metrics
- Getting Started with Logging - Core logging guide
- Logger Settings - Detailed configuration
- Log Archive - Long-term storage
- Using Log Aggregation - Trend analysis
- Using the Log Timer - Performance tracking
- Masking Log Messages - Security & privacy
- Logging in OmniStudio - OmniStudio integration
- RFLIB CLI Plugin - Automation tools
- Ops Center Overview - Central monitoring hub
- Logging Dashboard - View and analyze logs
- Management Console - System administration
- Permissions Explorer - Security validation
- Application Event Dashboard - Business insights
- Getting Started with the Trigger Framework - Metadata-driven triggers
- Getting Started with Retryable Actions - Reliable async processing
- Getting Started with HTTP Mocking - Integration testing
- Node / LWR Logger - Server-side Node.js / LWR logging
- Salesforce Functions - Serverless compute (EOL β see Node / LWR Logger)
- Logging to AWS CloudWatch - Cloud monitoring
- Logging to Azure App Insights - Azure observability
- Logging to OpenTelemetry - OTLP/HTTP forwarding
- Logging to Slack - Slack integrations
- Logging to Microsoft Teams - Teams integrations
- Logging to Google Chat - Google Chat integrations
- Getting Started with Pharos - External analytics
- [Outbound Callouts including the T