From c3561b843fded5f07aebd2cba461a5ebfb90bd17 Mon Sep 17 00:00:00 2001 From: Rasmus Foged Date: Fri, 7 Mar 2025 11:26:51 +0100 Subject: [PATCH 1/3] Adding fix, documentation and chloggen entry --- .chloggen/34188.yaml | 1 + exporter/azuremonitorexporter/README.md | 10 +++ .../azuremonitorexporter/log_to_envelope.go | 85 ++++++++++++++----- 3 files changed, 77 insertions(+), 19 deletions(-) diff --git a/.chloggen/34188.yaml b/.chloggen/34188.yaml index 16ddce4333684..2a3ce85ff3453 100644 --- a/.chloggen/34188.yaml +++ b/.chloggen/34188.yaml @@ -1,5 +1,6 @@ # Use this changelog template to create an entry for release notes. + # One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' change_type: enhancement diff --git a/exporter/azuremonitorexporter/README.md b/exporter/azuremonitorexporter/README.md index 029d63dc552b1..d5ebb3fad3784 100644 --- a/exporter/azuremonitorexporter/README.md +++ b/exporter/azuremonitorexporter/README.md @@ -118,6 +118,16 @@ Exception events are saved to the Application Insights `exception` table. This exporter saves log records to Application Insights `traces` table. [TraceId](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/data-model.md#field-traceid) is mapped to `operation_id` column and [SpanId](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/data-model.md#field-spanid) is mapped to `operation_parentId` column. +### Exceptions + +This exporter saves exception records to Application Insights `exceptions` table when log records indicate an excetion [specification](https://opentelemetry.io/docs/specs/otel/trace/exceptions/). + +Exceptions must be log records with `SeverityNumber` of 17 (Error) and the following attributes MUST be filled out: + +* exception.message +* exception.stacktrace +* exception.type + ### Metrics This exporter saves metrics to Application Insights `customMetrics` table. diff --git a/exporter/azuremonitorexporter/log_to_envelope.go b/exporter/azuremonitorexporter/log_to_envelope.go index 0c520e217c01d..c55a67b457dee 100644 --- a/exporter/azuremonitorexporter/log_to_envelope.go +++ b/exporter/azuremonitorexporter/log_to_envelope.go @@ -7,11 +7,11 @@ import ( "time" "github.com/microsoft/ApplicationInsights-Go/appinsights/contracts" + "github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal/traceutil" "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/pdata/plog" + conventions "go.opentelemetry.io/collector/semconv/v1.12.0" "go.uber.org/zap" - - "github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal/traceutil" ) type logPacker struct { @@ -25,31 +25,64 @@ func (packer *logPacker) LogRecordToEnvelope(logRecord plog.LogRecord, resource data := contracts.NewData() - messageData := contracts.NewMessageData() - messageData.Properties = make(map[string]string) + if logRecord.SeverityNumber() == plog.SeverityNumberError { + // handle envelope for exceptions + exceptionData := contracts.NewExceptionData() + exceptionData.Properties = make(map[string]string) + exceptionData.SeverityLevel = packer.toAiSeverityLevel(logRecord.SeverityNumber()) + exceptionData.ProblemId = logRecord.SeverityText() + + exceptionAttributeMap := logRecord.Attributes() + exceptionDetails := mapIncomingAttributeMapExceptionDetail(exceptionAttributeMap) + exceptionData.Exceptions = append(exceptionData.Exceptions, exceptionDetails) + + envelope.Name = exceptionData.EnvelopeName("") + + data.BaseData = exceptionData + data.BaseType = exceptionData.BaseType() + envelope.Data = data + + envelope.Tags[contracts.OperationId] = traceutil.TraceIDToHexOrEmptyString(logRecord.TraceID()) + envelope.Tags[contracts.OperationParentId] = traceutil.SpanIDToHexOrEmptyString(logRecord.SpanID()) + + resourceAttributes := resource.Attributes() + applyResourcesToDataProperties(exceptionData.Properties, resourceAttributes) + applyInstrumentationScopeValueToDataProperties(exceptionData.Properties, instrumentationScope) + applyCloudTagsToEnvelope(envelope, resourceAttributes) + applyInternalSdkVersionTagToEnvelope(envelope) + + setAttributesAsProperties(logRecord.Attributes(), exceptionData.Properties) - messageData.SeverityLevel = packer.toAiSeverityLevel(logRecord.SeverityNumber()) + packer.sanitize(func() []string { return exceptionData.Sanitize() }) - messageData.Message = logRecord.Body().AsString() + } else { + // handle envelope for messages (traces) + messageData := contracts.NewMessageData() + messageData.Properties = make(map[string]string) - envelope.Tags[contracts.OperationId] = traceutil.TraceIDToHexOrEmptyString(logRecord.TraceID()) - envelope.Tags[contracts.OperationParentId] = traceutil.SpanIDToHexOrEmptyString(logRecord.SpanID()) + messageData.SeverityLevel = packer.toAiSeverityLevel(logRecord.SeverityNumber()) + messageData.Message = logRecord.Body().AsString() - envelope.Name = messageData.EnvelopeName("") + envelope.Name = messageData.EnvelopeName("") - data.BaseData = messageData - data.BaseType = messageData.BaseType() - envelope.Data = data + data.BaseData = messageData + data.BaseType = messageData.BaseType() + envelope.Data = data - resourceAttributes := resource.Attributes() - applyResourcesToDataProperties(messageData.Properties, resourceAttributes) - applyInstrumentationScopeValueToDataProperties(messageData.Properties, instrumentationScope) - applyCloudTagsToEnvelope(envelope, resourceAttributes) - applyInternalSdkVersionTagToEnvelope(envelope) + envelope.Tags[contracts.OperationId] = traceutil.TraceIDToHexOrEmptyString(logRecord.TraceID()) + envelope.Tags[contracts.OperationParentId] = traceutil.SpanIDToHexOrEmptyString(logRecord.SpanID()) - setAttributesAsProperties(logRecord.Attributes(), messageData.Properties) + resourceAttributes := resource.Attributes() + applyResourcesToDataProperties(messageData.Properties, resourceAttributes) + applyInstrumentationScopeValueToDataProperties(messageData.Properties, instrumentationScope) + applyCloudTagsToEnvelope(envelope, resourceAttributes) + applyInternalSdkVersionTagToEnvelope(envelope) + + setAttributesAsProperties(logRecord.Attributes(), messageData.Properties) + + packer.sanitize(func() []string { return messageData.Sanitize() }) + } - packer.sanitize(func() []string { return messageData.Sanitize() }) packer.sanitize(func() []string { return envelope.Sanitize() }) packer.sanitize(func() []string { return contracts.SanitizeTags(envelope.Tags) }) @@ -97,3 +130,17 @@ func timestampFromLogRecord(lr plog.LogRecord) pcommon.Timestamp { return pcommon.NewTimestampFromTime(timeNow()) } + +func mapIncomingAttributeMapExceptionDetail(attributemap pcommon.Map) *contracts.ExceptionDetails { + exceptionDetails := contracts.NewExceptionDetails() + if message, exists := attributemap.Get(conventions.AttributeExceptionMessage); exists { + exceptionDetails.Message = message.Str() + } + if typeName, exists := attributemap.Get(conventions.AttributeExceptionType); exists { + exceptionDetails.TypeName = typeName.Str() + } + if stackTrace, exists := attributemap.Get(conventions.AttributeExceptionStacktrace); exists { + exceptionDetails.Stack = stackTrace.Str() + } + return exceptionDetails +} From b28514bd17e44862b379e33855ff977d39599dbe Mon Sep 17 00:00:00 2001 From: CloudFy <31371919+cloudfy@users.noreply.github.com> Date: Sat, 8 Mar 2025 15:10:37 +0100 Subject: [PATCH 2/3] Update .chloggen/34188.yaml Co-authored-by: Antoine Toulme --- .chloggen/34188.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.chloggen/34188.yaml b/.chloggen/34188.yaml index 2a3ce85ff3453..16ddce4333684 100644 --- a/.chloggen/34188.yaml +++ b/.chloggen/34188.yaml @@ -1,6 +1,5 @@ # Use this changelog template to create an entry for release notes. - # One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' change_type: enhancement From 39fb50dc41857c4204e45afed75fd480e83294ef Mon Sep 17 00:00:00 2001 From: Rasmus Foged Date: Tue, 11 Mar 2025 14:25:56 +0100 Subject: [PATCH 3/3] Aligning specification of exception to Azure SDK per comment of @hgaol. This moves the classification away from SeverityNumber to follow the semantec model specification instead. --- .../azuremonitorexporter/log_to_envelope.go | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/exporter/azuremonitorexporter/log_to_envelope.go b/exporter/azuremonitorexporter/log_to_envelope.go index c55a67b457dee..b0650b26eefc1 100644 --- a/exporter/azuremonitorexporter/log_to_envelope.go +++ b/exporter/azuremonitorexporter/log_to_envelope.go @@ -25,15 +25,16 @@ func (packer *logPacker) LogRecordToEnvelope(logRecord plog.LogRecord, resource data := contracts.NewData() - if logRecord.SeverityNumber() == plog.SeverityNumberError { + logAttributeMap := logRecord.Attributes() + + if hasRequiredKeys(logAttributeMap, conventions.AttributeExceptionMessage, conventions.AttributeExceptionType) { // handle envelope for exceptions exceptionData := contracts.NewExceptionData() exceptionData.Properties = make(map[string]string) exceptionData.SeverityLevel = packer.toAiSeverityLevel(logRecord.SeverityNumber()) exceptionData.ProblemId = logRecord.SeverityText() - exceptionAttributeMap := logRecord.Attributes() - exceptionDetails := mapIncomingAttributeMapExceptionDetail(exceptionAttributeMap) + exceptionDetails := mapIncomingAttributeMapExceptionDetail(logAttributeMap) exceptionData.Exceptions = append(exceptionData.Exceptions, exceptionDetails) envelope.Name = exceptionData.EnvelopeName("") @@ -51,7 +52,7 @@ func (packer *logPacker) LogRecordToEnvelope(logRecord plog.LogRecord, resource applyCloudTagsToEnvelope(envelope, resourceAttributes) applyInternalSdkVersionTagToEnvelope(envelope) - setAttributesAsProperties(logRecord.Attributes(), exceptionData.Properties) + setAttributesAsProperties(logAttributeMap, exceptionData.Properties) packer.sanitize(func() []string { return exceptionData.Sanitize() }) @@ -78,7 +79,7 @@ func (packer *logPacker) LogRecordToEnvelope(logRecord plog.LogRecord, resource applyCloudTagsToEnvelope(envelope, resourceAttributes) applyInternalSdkVersionTagToEnvelope(envelope) - setAttributesAsProperties(logRecord.Attributes(), messageData.Properties) + setAttributesAsProperties(logAttributeMap, messageData.Properties) packer.sanitize(func() []string { return messageData.Sanitize() }) } @@ -144,3 +145,12 @@ func mapIncomingAttributeMapExceptionDetail(attributemap pcommon.Map) *contracts } return exceptionDetails } +func hasRequiredKeys(attrMap pcommon.Map, keys ...string) bool { + for _, key := range keys { + _, exists := attrMap.Get(key) + if !exists { + return false + } + } + return true +}