| File: Otlp\Model\OtlpLogEntryData.cs | Web Access |
| Project: src\src\Aspire.Dashboard\Aspire.Dashboard.csproj (Aspire.Dashboard) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using OpenTelemetry.Proto.Logs.V1; using SeverityNumberProto = OpenTelemetry.Proto.Logs.V1.SeverityNumber; namespace Aspire.Dashboard.Otlp.Model; internal class OtlpLogEntryData { internal OtlpLogEntryData(LogRecord record, OtlpResourceView resourceView, OtlpScope scope, OtlpContext context) { ResourceView = resourceView; Scope = scope; TimeStamp = ResolveTimeStamp(record); string? originalFormat = null; string? parentId = null; string? eventNameFromAttribute = null; Attributes = record.Attributes.ToKeyValuePairs(context, filter: attribute => { switch (attribute.Key) { case "{OriginalFormat}": originalFormat = attribute.Value.GetString(); return false; case "ParentId": parentId = attribute.Value.GetString(); return false; case "SpanId": case "TraceId": case OtlpHelpers.AspireLogIdAttribute: return false; case "logrecord.event.name": case "event.name": eventNameFromAttribute ??= attribute.Value.GetString(); return false; default: return true; } }); Flags = record.Flags; SeverityNumber = (int)record.SeverityNumber; Severity = MapSeverity(record.SeverityNumber); Message = record.Body is { } body ? OtlpHelpers.TruncateString(body.GetString(), context.Options.MaxAttributeLength) : string.Empty; OriginalFormat = originalFormat; SpanId = record.SpanId.ToHexString(); TraceId = record.TraceId.ToHexString(); ParentId = parentId ?? string.Empty; // EventName from the LogRecord field takes precedence over the legacy attribute. EventName = !string.IsNullOrEmpty(record.EventName) ? record.EventName : eventNameFromAttribute; } internal DateTime TimeStamp { get; } internal uint Flags { get; } internal LogLevel Severity { get; } internal int SeverityNumber { get; } internal string Message { get; } internal string SpanId { get; } internal string TraceId { get; } internal string ParentId { get; } internal string? OriginalFormat { get; } internal OtlpResourceView ResourceView { get; } internal OtlpScope Scope { get; } internal KeyValuePair<string, string>[] Attributes { get; } internal string? EventName { get; } internal OtlpLogEntry CreateLogEntry(long internalId) => new( internalId, TimeStamp, Flags, Severity, SeverityNumber, Message, SpanId, TraceId, ParentId, OriginalFormat, ResourceView, Scope, Attributes, EventName); private static DateTime ResolveTimeStamp(LogRecord record) { // OpenTelemetry recommends using time_unix_nano when present and falling back to observed_time_unix_nano. // https://opentelemetry.io/docs/specs/otel/logs/data-model/#field-timestamp var resolvedTimeUnixNano = record.TimeUnixNano != 0 ? record.TimeUnixNano : record.ObservedTimeUnixNano; return OtlpHelpers.UnixNanoSecondsToDateTime(resolvedTimeUnixNano); } private static LogLevel MapSeverity(SeverityNumberProto severityNumber) => severityNumber switch { SeverityNumberProto.Trace or SeverityNumberProto.Trace2 or SeverityNumberProto.Trace3 or SeverityNumberProto.Trace4 => LogLevel.Trace, SeverityNumberProto.Debug or SeverityNumberProto.Debug2 or SeverityNumberProto.Debug3 or SeverityNumberProto.Debug4 => LogLevel.Debug, SeverityNumberProto.Info or SeverityNumberProto.Info2 or SeverityNumberProto.Info3 or SeverityNumberProto.Info4 => LogLevel.Information, SeverityNumberProto.Warn or SeverityNumberProto.Warn2 or SeverityNumberProto.Warn3 or SeverityNumberProto.Warn4 => LogLevel.Warning, SeverityNumberProto.Error or SeverityNumberProto.Error2 or SeverityNumberProto.Error3 or SeverityNumberProto.Error4 => LogLevel.Error, SeverityNumberProto.Fatal or SeverityNumberProto.Fatal2 or SeverityNumberProto.Fatal3 or SeverityNumberProto.Fatal4 => LogLevel.Critical, _ => LogLevel.None }; }