@@ -216,6 +216,7 @@ global with sharing class LoggerRestResource {
216216 return postResponse ;
217217 }
218218 }
219+
219220 private void saveLog (OTelLogsPayload logsPayload ) {
220221 LoggerDataStore .getEventBus ().publishRecords (logsPayload .getConvertedLogEntryEvents ());
221222 }
@@ -269,8 +270,10 @@ global with sharing class LoggerRestResource {
269270 List <LogEntryEvent__e > logEntryEvents = new List <LogEntryEvent__e >();
270271
271272 for (OTelScopeLog scopeLog : this .scopeLogs ) {
273+ Integer transactionEntryNumber = 1 ;
272274 for (OTelLogRecord otelLogEntry : scopeLog .logRecords ) {
273275 LogEntryEvent__e convertedLogEntryEvent = otelLogEntry .getLogEntryEvent ();
276+ convertedLogEntryEvent .TransactionEntryNumber__c = transactionEntryNumber ++ ;
274277 Map <Schema .SObjectField , Object > supplementalFieldToValue = this .resource .convertAttributes ();
275278 for (Schema .SObjectField field : supplementalFieldToValue .keySet ()) {
276279 convertedLogEntryEvent .put (field , supplementalFieldToValue .get (field ));
@@ -283,27 +286,26 @@ global with sharing class LoggerRestResource {
283286 }
284287 }
285288
286- // OTel supports additional types boolValue, float64Value, and intValue
289+ // OTel supports an additional type float64Value
287290 // but there's not currently a need for them in Nebula Logger's data model
288- // As more mappings are added, these types will be re-added when needed
289291 public class OTelAttribute {
290292 public final String key ;
291293 public final OTelAttributeValue value ;
292294
293- // public OTelAttribute(String key, Boolean value) {
294- // this.key = key;
295- // this.value = new OTelAttributeValue(value);
296- // }
295+ public OTelAttribute (String key , Boolean value ) {
296+ this .key = key ;
297+ this .value = new OTelAttributeValue (value );
298+ }
297299
298300 // public OTelAttribute(String key, Decimal value) {
299301 // this.key = key;
300302 // this.value = new OTelAttributeValue(value);
301303 // }
302304
303- // public OTelAttribute(String key, Integer value) {
304- // this.key = key;
305- // this.value = new OTelAttributeValue(value);
306- // }
305+ public OTelAttribute (String key , Integer value ) {
306+ this .key = key ;
307+ this .value = new OTelAttributeValue (value );
308+ }
307309
308310 public OTelAttribute (String key , String value ) {
309311 this .key = key ;
@@ -312,22 +314,22 @@ global with sharing class LoggerRestResource {
312314 }
313315
314316 public class OTelAttributeValue {
315- // public final Boolean boolValue;
317+ public final Boolean boolValue ;
316318 // public final Decimal float64Value;
317- // public final Integer intValue;
319+ public final Integer intValue ;
318320 public final String stringValue ;
319321
320- // public OTelAttributeValue(Boolean value) {
321- // this.boolValue = value;
322- // }
322+ public OTelAttributeValue (Boolean value ) {
323+ this .boolValue = value ;
324+ }
323325
324326 // public OTelAttributeValue(Decimal value) {
325327 // this.float64Value = value;
326328 // }
327329
328- // public OTelAttributeValue(Integer value) {
329- // this.intValue = value;
330- // }
330+ public OTelAttributeValue (Integer value ) {
331+ this .intValue = value ;
332+ }
331333
332334 public OTelAttributeValue (String value ) {
333335 this .stringValue = value ;
@@ -368,21 +370,32 @@ global with sharing class LoggerRestResource {
368370 public class OTelLogRecord {
369371 public List <OTelAttribute > attributes = new List <OTelAttribute >();
370372 public OTelAttributeValue body ;
373+ public String name ;
374+ public Integer severityNumber ;
371375 public String severityText ;
372- public String timeUnixNano = ( System . now (). getTime () * 1000000 ). toString ();
373- // TODO revisit mappings for spanId and traceId
374- // public String spanId ;
375- // public String traceId;
376+ // TODO revisit mapping for spanId
377+ public String spanId ;
378+ public String timeUnixNano ;
379+ public String traceId ;
376380
377381 private transient LogEntryEvent__e convertedLogEntryEvent ;
378382
379383 public LogEntryEvent__e getLogEntryEvent () {
380384 if (this .convertedLogEntryEvent == null ) {
381385 System .LoggingLevel entryLoggingLevel = this .getLoggingLevel ();
382- Long entryEpochTimestamp = Long .valueOf (this .timeUnixNano ) / 1000000 ;
383- Datetime entryTimestamp = Datetime .newInstance (entryEpochTimestamp );
386+ Long entryEpochTimestamp = timeUnixNano == null ? null : Long .valueOf (this .timeUnixNano ) / 1000000 ;
387+ Datetime entryTimestamp = timeUnixNano == null ? null : Datetime .newInstance (entryEpochTimestamp );
388+ String convertedTraceId = this .convertTraceId ();
389+
390+ LogEntryEventBuilder builder = Logger .newEntry (entryLoggingLevel , this .body ?. stringValue );
391+ if (entryTimestamp != null ) {
392+ builder .setTimestamp (entryTimestamp );
393+ }
394+ this .convertedLogEntryEvent = builder .getLogEntryEvent ();
395+ this .convertedLogEntryEvent .EntryScenario__c = this .name ;
396+ this .convertedLogEntryEvent .OriginType__c = ' API' ;
397+ this .convertedLogEntryEvent .TransactionId__c = convertedTraceId ;
384398
385- this .convertedLogEntryEvent = Logger .newEntry (entryLoggingLevel , this .body ?. stringValue ).setTimestamp (entryTimestamp ).getLogEntryEvent ();
386399 // Since the log entries originate off-platform, tracking the limits usage isn't really relevant here
387400 this .convertedLogEntryEvent .LimitsAggregateQueriesMax__c = null ;
388401 this .convertedLogEntryEvent .LimitsAggregateQueriesUsed__c = null ;
@@ -416,12 +429,28 @@ global with sharing class LoggerRestResource {
416429 this .convertedLogEntryEvent .LimitsSoqlQueryRowsUsed__c = null ;
417430 this .convertedLogEntryEvent .LimitsSoslSearchesMax__c = null ;
418431 this .convertedLogEntryEvent .LimitsSoslSearchesUsed__c = null ;
432+
433+ // Since the log entries originate off-platform, the loggedBy user
434+ // may not be the API user creating the logs, so clear the related fields
435+ this .convertedLogEntryEvent .Locale__c = null ;
436+ this .convertedLogEntryEvent .LoggedById__c = null ;
437+ this .convertedLogEntryEvent .ProfileId__c = null ;
438+ this .convertedLogEntryEvent .ThemeDisplayed__c = null ;
439+ this .convertedLogEntryEvent .TimeZoneId__c = null ;
440+ this .convertedLogEntryEvent .TimeZoneName__c = null ;
441+ this .convertedLogEntryEvent .UserLicenseDefinitionKey__c = null ;
442+ this .convertedLogEntryEvent .UserLicenseId__c = null ;
443+ this .convertedLogEntryEvent .UserLicenseName__c = null ;
444+ this .convertedLogEntryEvent .UserRoleId__c = null ;
445+ this .convertedLogEntryEvent .UserRoleName__c = null ;
446+ this .convertedLogEntryEvent .UserType__c = null ;
447+
448+ // Clear irrelevant origin fields
419449 this .convertedLogEntryEvent .OriginLocation__c = null ;
420450 this .convertedLogEntryEvent .OriginSourceActionName__c = null ;
421451 this .convertedLogEntryEvent .OriginSourceApiName__c = null ;
422452 this .convertedLogEntryEvent .OriginSourceId__c = null ;
423453 this .convertedLogEntryEvent .OriginSourceMetadataType__c = null ;
424- this .convertedLogEntryEvent .OriginType__c = ' API' ;
425454 this .convertedLogEntryEvent .StackTrace__c = null ;
426455
427456 Map <Schema .SObjectField , Object > supplementalFieldToValue = this .convertAttributes ();
@@ -433,27 +462,58 @@ global with sharing class LoggerRestResource {
433462 return this .convertedLogEntryEvent ;
434463 }
435464
465+ private Map <Integer , String > getSeverityNumberToTextMapping () {
466+ return new Map <Integer , String >{
467+ 1 = > ' TRACE' ,
468+ 2 = > ' TRACE2' ,
469+ 3 = > ' TRACE3' ,
470+ 4 = > ' TRACE4' ,
471+ 5 = > ' DEBUG' ,
472+ 6 = > ' DEBUG2' ,
473+ 7 = > ' DEBUG3' ,
474+ 8 = > ' DEBUG4' ,
475+ 9 = > ' INFO' ,
476+ 10 = > ' INFO2' ,
477+ 11 = > ' INFO3' ,
478+ 12 = > ' INFO4' ,
479+ 13 = > ' WARN' ,
480+ 14 = > ' WARN2' ,
481+ 15 = > ' WARN3' ,
482+ 16 = > ' WARN4' ,
483+ 17 = > ' ERROR' ,
484+ 18 = > ' ERROR2' ,
485+ 19 = > ' ERROR3' ,
486+ 20 = > ' ERROR4' ,
487+ 21 = > ' FATAL' ,
488+ 22 = > ' FATAL2' ,
489+ 23 = > ' FATAL3' ,
490+ 24 = > ' FATAL4'
491+ };
492+ }
493+
436494 private System.LoggingLevel getLoggingLevel () {
437- switch on this .severityText ?. toLowerCase () {
438- when ' error' {
495+ String severityText = this .severityText ?? this .getSeverityNumberToTextMapping ().get (this .severityNumber );
496+ // Docs: https://opentelemetry.io/docs/specs/otel/logs/data-model/#field-severitytext
497+ switch on severityText ?. toUpperCase () {
498+ when ' FATAL4' , 'FATAL3', 'FATAL2', 'FATAL', 'ERROR4', 'ERROR3', 'ERROR2', ' ERROR' {
439499 return System .LoggingLevel .ERROR ;
440500 }
441- when ' warn ' {
501+ when ' WARN4 ' , 'WARN3', 'WARN2', ' WARN ' {
442502 return System .LoggingLevel .WARN ;
443503 }
444- when ' info ' {
504+ when ' INFO4 ' , 'INFO3', 'INFO2', ' INFO ' {
445505 return System .LoggingLevel .INFO ;
446506 }
447- when ' debug ' {
507+ when ' DEBUG4 ' , 'DEBUG3', 'DEBUG2', ' DEBUG ' {
448508 return System .LoggingLevel .DEBUG ;
449509 }
450- when ' trace3 ' {
510+ when ' TRACE4 ' , ' TRACE3 ' {
451511 return System .LoggingLevel .FINE ;
452512 }
453- when ' trace2 ' {
513+ when ' TRACE2 ' {
454514 return System .LoggingLevel .FINER ;
455515 }
456- when ' trace ' {
516+ when ' TRACE ' {
457517 return System .LoggingLevel .FINEST ;
458518 }
459519 when else {
@@ -469,6 +529,24 @@ global with sharing class LoggerRestResource {
469529
470530 for (OTelAttribute entryAttribute : this .attributes ) {
471531 switch on entryAttribute .key {
532+ when ' browser.address' {
533+ supplementalFieldToValue .put (LogEntryEvent__e .BrowserAddress__c , entryAttribute .value ?. stringValue );
534+ }
535+ when ' browser.form_factor' {
536+ supplementalFieldToValue .put (LogEntryEvent__e .BrowserFormFactor__c , entryAttribute .value ?. stringValue );
537+ }
538+ when ' browser.language' {
539+ supplementalFieldToValue .put (LogEntryEvent__e .BrowserLanguage__c , entryAttribute .value ?. stringValue );
540+ }
541+ when ' browser.screen_resolution' {
542+ supplementalFieldToValue .put (LogEntryEvent__e .BrowserScreenResolution__c , entryAttribute .value ?. stringValue );
543+ }
544+ when ' browser.user_agent' {
545+ supplementalFieldToValue .put (LogEntryEvent__e .BrowserUserAgent__c , entryAttribute .value ?. stringValue );
546+ }
547+ when ' browser.window_resolution' {
548+ supplementalFieldToValue .put (LogEntryEvent__e .BrowserWindowResolution__c , entryAttribute .value ?. stringValue );
549+ }
472550 when ' exception.message' {
473551 supplementalFieldToValue .put (LogEntryEvent__e .ExceptionMessage__c , entryAttribute .value ?. stringValue );
474552 }
@@ -478,6 +556,54 @@ global with sharing class LoggerRestResource {
478556 when ' exception.type' {
479557 supplementalFieldToValue .put (LogEntryEvent__e .ExceptionType__c , entryAttribute .value ?. stringValue );
480558 }
559+ when ' http_request.body' {
560+ supplementalFieldToValue .put (LogEntryEvent__e .HttpRequestBody__c , entryAttribute .value ?. stringValue );
561+ }
562+ when ' http_request.body_masked' {
563+ supplementalFieldToValue .put (LogEntryEvent__e .HttpRequestBodyMasked__c , entryAttribute .value ?. boolValue );
564+ }
565+ when ' http_request.compressed' {
566+ supplementalFieldToValue .put (LogEntryEvent__e .HttpRequestCompressed__c , entryAttribute .value ?. boolValue );
567+ }
568+ when ' http_request.endpoint' {
569+ supplementalFieldToValue .put (LogEntryEvent__e .HttpRequestEndpoint__c , entryAttribute .value ?. stringValue );
570+ }
571+ when ' http_request.header_keys' {
572+ supplementalFieldToValue .put (LogEntryEvent__e .HttpRequestHeaderKeys__c , entryAttribute .value ?. stringValue );
573+ }
574+ when ' http_request.headers' {
575+ supplementalFieldToValue .put (LogEntryEvent__e .HttpRequestHeaders__c , entryAttribute .value ?. stringValue );
576+ }
577+ when ' http_request.method' {
578+ supplementalFieldToValue .put (LogEntryEvent__e .HttpRequestMethod__c , entryAttribute .value ?. stringValue );
579+ }
580+ when ' http_response.body' {
581+ supplementalFieldToValue .put (LogEntryEvent__e .HttpResponseBody__c , entryAttribute .value ?. stringValue );
582+ }
583+ when ' http_response.body_masked' {
584+ supplementalFieldToValue .put (LogEntryEvent__e .HttpResponseBodyMasked__c , entryAttribute .value ?. boolValue );
585+ }
586+ when ' http_response.header_keys' {
587+ supplementalFieldToValue .put (LogEntryEvent__e .HttpResponseHeaderKeys__c , entryAttribute .value ?. stringValue );
588+ }
589+ when ' http_response.headers' {
590+ supplementalFieldToValue .put (LogEntryEvent__e .HttpResponseHeaders__c , entryAttribute .value ?. stringValue );
591+ }
592+ when ' http_response.status' {
593+ supplementalFieldToValue .put (LogEntryEvent__e .HttpResponseStatus__c , entryAttribute .value ?. stringValue );
594+ }
595+ when ' http_response.status_code' {
596+ supplementalFieldToValue .put (LogEntryEvent__e .HttpResponseStatusCode__c , entryAttribute .value ?. intValue );
597+ }
598+ when ' logged_by.federation_identifier' {
599+ supplementalFieldToValue .put (LogEntryEvent__e .LoggedByFederationIdentifier__c , entryAttribute .value ?. stringValue );
600+ }
601+ when ' logged_by.id' {
602+ supplementalFieldToValue .put (LogEntryEvent__e .LoggedById__c , entryAttribute .value ?. stringValue );
603+ }
604+ when ' logged_by.username' {
605+ supplementalFieldToValue .put (LogEntryEvent__e .LoggedByUsername__c , entryAttribute .value ?. stringValue );
606+ }
481607 when ' origin.stack_trace' {
482608 supplementalFieldToValue .put (LogEntryEvent__e .StackTrace__c , entryAttribute .value ?. stringValue );
483609 }
@@ -489,5 +615,24 @@ global with sharing class LoggerRestResource {
489615
490616 return supplementalFieldToValue ;
491617 }
618+
619+ private String convertTraceId () {
620+ if (String .isBlank (this .traceId )) {
621+ return null ;
622+ }
623+
624+ String hyphenatedUuid =
625+ this .traceId .substring (0 , 8 ) +
626+ ' -' +
627+ this .traceId .substring (8 , 12 ) +
628+ ' -' +
629+ this .traceId .substring (12 , 16 ) +
630+ ' -' +
631+ this .traceId .substring (16 , 20 ) +
632+ ' -' +
633+ this .traceId .substring (20 , 32 );
634+
635+ return hyphenatedUuid ;
636+ }
492637 }
493638}
0 commit comments