diff --git a/exporter/azuremonitorexporter/README.md b/exporter/azuremonitorexporter/README.md index 029d63dc552b..d5ebb3fad378 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 0c520e217c01..b0650b26eefc 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,65 @@ func (packer *logPacker) LogRecordToEnvelope(logRecord plog.LogRecord, resource data := contracts.NewData() - messageData := contracts.NewMessageData() - messageData.Properties = make(map[string]string) + 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() + + exceptionDetails := mapIncomingAttributeMapExceptionDetail(logAttributeMap) + 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(logAttributeMap, 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(logAttributeMap, 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 +131,26 @@ 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 +} +func hasRequiredKeys(attrMap pcommon.Map, keys ...string) bool { + for _, key := range keys { + _, exists := attrMap.Get(key) + if !exists { + return false + } + } + return true +}