package api import ( "context" "fmt" "net/url" "testing" "time" "buf.build/go/protovalidate" auditV1 "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/gen/go/audit/v1" internalAuditApi "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/internal/audit/api" pkgAuditCommon "dev.azure.com/schwarzit/schwarzit.stackit-public/audit-go.git/pkg/audit/common" "github.com/google/uuid" "github.com/stretchr/testify/assert" otelLog "go.opentelemetry.io/otel/log" "go.opentelemetry.io/otel/log/global" otelSdkLog "go.opentelemetry.io/otel/sdk/log" "go.opentelemetry.io/otel/trace" otlpCommonV1 "go.opentelemetry.io/proto/otlp/common/v1" otlpLogsV1 "go.opentelemetry.io/proto/otlp/logs/v1" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/wrapperspb" ) func Test_TelemetryHubAuditApi(t *testing.T) { validator, err := protovalidate.New() assert.NoError(t, err) api, err := NewTelemetryHubAuditApi(validator, "workerId", "eu01") assert.NoError(t, err) t.Run("organization event", func(t *testing.T) { event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil) routableIdentifier := pkgAuditCommon.NewRoutableIdentifier(objectIdentifier) cloudEvent, err := api.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, routableIdentifier) assert.NoError(t, err) assert.NotNil(t, cloudEvent) }) t.Run("folder event", func(t *testing.T) { event, objectIdentifier := internalAuditApi.NewFolderAuditEvent(nil) routableIdentifier := pkgAuditCommon.NewRoutableIdentifier(objectIdentifier) cloudEvent, err := api.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, routableIdentifier) assert.NoError(t, err) assert.NotNil(t, cloudEvent) }) t.Run("project event", func(t *testing.T) { event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(nil) routableIdentifier := pkgAuditCommon.NewRoutableIdentifier(objectIdentifier) cloudEvent, err := api.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, routableIdentifier) assert.NoError(t, err) assert.NotNil(t, cloudEvent) }) t.Run("system event", func(t *testing.T) { event := internalAuditApi.NewSystemAuditEvent(nil) routableIdentifier := pkgAuditCommon.RoutableSystemIdentifier cloudEvent, err := api.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PRIVATE, routableIdentifier) assert.NoError(t, err) assert.NotNil(t, cloudEvent) }) t.Run("invalid event", func(t *testing.T) { event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(nil) routableIdentifier := pkgAuditCommon.NewRoutableIdentifier(objectIdentifier) apiWithInvalidRegion, err := NewTelemetryHubAuditApi(validator, "workerId", "invalid") assert.NoError(t, err) cloudEvent, err := apiWithInvalidRegion.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, routableIdentifier) assert.EqualError(t, err, "telemetry hub record validation: validation error: cloud.region attribute is required and should be global or eu01, eu02, ...") assert.Nil(t, cloudEvent) }) } func Test_TelemetryHubAuditSendApi(t *testing.T) { validator, err := protovalidate.New() assert.NoError(t, err) // audit api auditApi, err := NewTelemetryHubAuditApi(validator, "workerId", "eu01") assert.NoError(t, err) // telemetry hub logger pool loggerPool, err := NewTelemetryHubLoggerPool(1, NewMockTelemetryHubLoggerOption()) assert.NoError(t, err) logger := loggerPool.Get().(*MockTelemetryHubLogger) // audit send api auditSendApi, err := NewTelemetryAuditSendApi(loggerPool, "workerId", "eu01") assert.NoError(t, err) t.Run("organization event", func(t *testing.T) { event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil) routableIdentifier := pkgAuditCommon.NewRoutableIdentifier(objectIdentifier) ctx := context.Background() cloudEvent, err := auditApi.ValidateAndSerialize(ctx, event, auditV1.Visibility_VISIBILITY_PUBLIC, routableIdentifier) assert.NoError(t, err) assert.NoError(t, auditSendApi.Send(ctx, routableIdentifier, cloudEvent)) record := logger.GetRecord() attributeConstraints := NewAttributesConstraints( newConstraint("service.name", "resource-manager"), newConstraint("service.instance.id", "workerId"), newConstraint("cloud.region", "eu01"), newConstraint("stackit.resource.type", "ORGANIZATION"), newConstraint("stackit.resource.id", objectIdentifier.Identifier), newConstraint("stackit.log.id", IdFromRequestAttributes(event)), newConstraint("stackit.log.type", "AUDIT"), newConstraint("stackit.action", "resourcemanager.organization.created"), newConstraint("stackit.visibility", "PUBLIC"), ) record.WalkAttributes(attributeConstraints.assertAttributesIfSpecifiedFunc(t)) assert.Empty(t, attributeConstraints.constraintMap) }) t.Run("folder event", func(t *testing.T) { event, objectIdentifier := internalAuditApi.NewFolderAuditEvent(nil) routableIdentifier := pkgAuditCommon.NewRoutableIdentifier(objectIdentifier) cloudEvent, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, routableIdentifier) assert.NoError(t, err) assert.NoError(t, auditSendApi.Send(context.Background(), routableIdentifier, cloudEvent)) record := logger.GetRecord() attributeConstraints := NewAttributesConstraints( newConstraint("service.name", "resource-manager"), newConstraint("service.instance.id", "workerId"), newConstraint("cloud.region", "eu01"), newConstraint("stackit.resource.type", "FOLDER"), newConstraint("stackit.resource.id", objectIdentifier.Identifier), newConstraint("stackit.log.id", IdFromRequestAttributes(event)), newConstraint("stackit.log.type", "AUDIT"), newConstraint("stackit.action", "resourcemanager.folder.created"), newConstraint("stackit.visibility", "PUBLIC"), ) record.WalkAttributes(attributeConstraints.assertAttributesIfSpecifiedFunc(t)) assert.Empty(t, attributeConstraints.constraintMap) }) t.Run("project event", func(t *testing.T) { event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(nil) routableIdentifier := pkgAuditCommon.NewRoutableIdentifier(objectIdentifier) cloudEvent, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, routableIdentifier) assert.NoError(t, err) assert.NoError(t, auditSendApi.Send(context.Background(), routableIdentifier, cloudEvent)) record := logger.GetRecord() attributeConstraints := NewAttributesConstraints( newConstraint("service.name", "resource-manager"), newConstraint("service.instance.id", "workerId"), newConstraint("cloud.region", "eu01"), newConstraint("stackit.resource.type", "PROJECT"), newConstraint("stackit.resource.id", objectIdentifier.Identifier), newConstraint("stackit.log.id", IdFromRequestAttributes(event)), newConstraint("stackit.log.type", "AUDIT"), newConstraint("stackit.action", "resourcemanager.project.created"), newConstraint("stackit.visibility", "PUBLIC"), ) record.WalkAttributes(attributeConstraints.assertAttributesIfSpecifiedFunc(t)) assert.Empty(t, attributeConstraints.constraintMap) }) t.Run("system event", func(t *testing.T) { event := internalAuditApi.NewSystemAuditEvent(nil) routableIdentifier := pkgAuditCommon.RoutableSystemIdentifier cloudEvent, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PRIVATE, routableIdentifier) assert.NoError(t, err) assert.NoError(t, auditSendApi.Send(context.Background(), routableIdentifier, cloudEvent)) record := logger.GetRecord() attributeConstraints := NewAttributesConstraints( newConstraint("service.name", "resource-manager"), newConstraint("service.instance.id", "workerId"), newConstraint("cloud.region", "eu01"), newConstraint("stackit.resource.type", "SYSTEM"), newConstraint("stackit.resource.id", routableIdentifier.Identifier), newConstraint("stackit.log.id", IdFromRequestAttributes(event)), newConstraint("stackit.log.type", "AUDIT"), newConstraint("stackit.action", "resourcemanager.system.changed"), newConstraint("stackit.visibility", "INTERNAL"), ) record.WalkAttributes(attributeConstraints.assertAttributesIfSpecifiedFunc(t)) assert.Empty(t, attributeConstraints.constraintMap) }) t.Run("invalid event", func(t *testing.T) { event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(nil) routableIdentifier := pkgAuditCommon.NewRoutableIdentifier(objectIdentifier) cloudEvent, err := auditApi.ValidateAndSerialize(context.Background(), event, auditV1.Visibility_VISIBILITY_PUBLIC, routableIdentifier) assert.NoError(t, err) sendApiWithInvalidRegion, err := NewTelemetryAuditSendApi(loggerPool, "workerId", "invalid") assert.NoError(t, err) assert.EqualError(t, sendApiWithInvalidRegion.Send(context.Background(), routableIdentifier, cloudEvent), "send: validation error: cloud.region attribute is required and should be global or eu01, eu02, ...") }) t.Run("cloud event nil", func(t *testing.T) { assert.ErrorIs(t, auditSendApi.Send(context.Background(), pkgAuditCommon.RoutableSystemIdentifier, nil), pkgAuditCommon.ErrCloudEventNil) }) t.Run("object identifier nil", func(t *testing.T) { assert.ErrorIs(t, auditSendApi.Send(context.Background(), nil, &pkgAuditCommon.CloudEvent{}), pkgAuditCommon.ErrObjectIdentifierNil) }) t.Run("unsupported routable type", func(t *testing.T) { assert.ErrorIs(t, auditSendApi.Send(context.Background(), &pkgAuditCommon.RoutableIdentifier{Identifier: uuid.NewString(), Type: "INVALID"}, &pkgAuditCommon.CloudEvent{}), pkgAuditCommon.ErrUnsupportedRoutableType) }) t.Run("unsupported data type type", func(t *testing.T) { assert.NoError(t, auditSendApi.Send( context.Background(), pkgAuditCommon.RoutableSystemIdentifier, &pkgAuditCommon.CloudEvent{DataType: pkgAuditCommon.DataTypeLegacyAuditEventV1}), ) }) } func Test_InitLoggerProvider(t *testing.T) { loggerProvider := global.GetLoggerProvider() defer global.SetLoggerProvider(loggerProvider) validator, err := protovalidate.New() assert.NoError(t, err) t.Run("valid", func(t *testing.T) { assert.NoError(t, InitLoggerProvider(context.Background(), "localhost:8080", "token", "resource-manager", validator)) }) t.Run("invalid hub url", func(t *testing.T) { assert.EqualError(t, InitLoggerProvider(context.Background(), "otlp://localhost:8080", "token", "resource-manager", validator), "failed to create OTLP log exporter: parse \"https://otlp:%2F%2Flocalhost:8080/v1/logs\": invalid URL escape \"%2F\"", ) }) } func Test_convertVisibility(t *testing.T) { t.Run("visibility public", func(t *testing.T) { event := auditV1.RoutableAuditEvent{Visibility: auditV1.Visibility_VISIBILITY_PUBLIC} assert.Equal(t, visibilityPublic, convertVisibility(&event)) }) t.Run("visibility private", func(t *testing.T) { event := auditV1.RoutableAuditEvent{Visibility: auditV1.Visibility_VISIBILITY_PRIVATE} assert.Equal(t, visibilityInternal, convertVisibility(&event)) }) t.Run("visibility unspecified", func(t *testing.T) { event := auditV1.RoutableAuditEvent{Visibility: auditV1.Visibility_VISIBILITY_UNSPECIFIED} assert.Equal(t, visibilityInternal, convertVisibility(&event)) }) } func Test_convertSeverity(t *testing.T) { t.Run("severity debug", func(t *testing.T) { event := auditV1.AuditLogEntry{Severity: auditV1.LogSeverity_LOG_SEVERITY_DEBUG} severity, severityText, err := convertSeverity(&event) assert.NoError(t, err) assert.Equal(t, otelLog.SeverityDebug, severity) assert.Equal(t, severityTextDebug, severityText) }) t.Run("severity info", func(t *testing.T) { event := auditV1.AuditLogEntry{Severity: auditV1.LogSeverity_LOG_SEVERITY_INFO} severity, severityText, err := convertSeverity(&event) assert.NoError(t, err) assert.Equal(t, otelLog.SeverityInfo, severity) assert.Equal(t, severityTextInfo, severityText) }) t.Run("severity notice", func(t *testing.T) { event := auditV1.AuditLogEntry{Severity: auditV1.LogSeverity_LOG_SEVERITY_NOTICE} severity, severityText, err := convertSeverity(&event) assert.NoError(t, err) assert.Equal(t, otelLog.SeverityInfo, severity) assert.Equal(t, severityTextInfo, severityText) }) t.Run("severity default", func(t *testing.T) { event := auditV1.AuditLogEntry{Severity: auditV1.LogSeverity_LOG_SEVERITY_DEFAULT} severity, severityText, err := convertSeverity(&event) assert.NoError(t, err) assert.Equal(t, otelLog.SeverityInfo, severity) assert.Equal(t, severityTextInfo, severityText) }) t.Run("severity warn", func(t *testing.T) { event := auditV1.AuditLogEntry{Severity: auditV1.LogSeverity_LOG_SEVERITY_WARNING} severity, severityText, err := convertSeverity(&event) assert.NoError(t, err) assert.Equal(t, otelLog.SeverityWarn, severity) assert.Equal(t, severityTextWarn, severityText) }) t.Run("severity error", func(t *testing.T) { event := auditV1.AuditLogEntry{Severity: auditV1.LogSeverity_LOG_SEVERITY_ERROR} severity, severityText, err := convertSeverity(&event) assert.NoError(t, err) assert.Equal(t, otelLog.SeverityError, severity) assert.Equal(t, severityTextError, severityText) }) t.Run("severity critical", func(t *testing.T) { event := auditV1.AuditLogEntry{Severity: auditV1.LogSeverity_LOG_SEVERITY_CRITICAL} severity, severityText, err := convertSeverity(&event) assert.NoError(t, err) assert.Equal(t, otelLog.SeverityFatal, severity) assert.Equal(t, severityTextFatal, severityText) }) t.Run("severity alert", func(t *testing.T) { event := auditV1.AuditLogEntry{Severity: auditV1.LogSeverity_LOG_SEVERITY_ALERT} severity, severityText, err := convertSeverity(&event) assert.NoError(t, err) assert.Equal(t, otelLog.SeverityFatal, severity) assert.Equal(t, severityTextFatal, severityText) }) t.Run("severity emergency", func(t *testing.T) { event := auditV1.AuditLogEntry{Severity: auditV1.LogSeverity_LOG_SEVERITY_EMERGENCY} severity, severityText, err := convertSeverity(&event) assert.NoError(t, err) assert.Equal(t, otelLog.SeverityFatal, severity) assert.Equal(t, severityTextFatal, severityText) }) t.Run("severity unknown", func(t *testing.T) { event := auditV1.AuditLogEntry{Severity: auditV1.LogSeverity(10000)} severity, severityText, err := convertSeverity(&event) assert.EqualError(t, err, "unsupported audit log entry severity: 10000") assert.Equal(t, otelLog.Severity(0), severity) assert.Equal(t, "", severityText) }) } func Test_convertRequestMethod(t *testing.T) { newEvent := func(method auditV1.AttributeContext_HttpMethod) auditV1.AuditLogEntry { return auditV1.AuditLogEntry{ ProtoPayload: &auditV1.AuditLog{ RequestMetadata: &auditV1.RequestMetadata{ RequestAttributes: &auditV1.AttributeContext_Request{ Method: method, }}}} } t.Run("request method get", func(t *testing.T) { event := newEvent(auditV1.AttributeContext_HTTP_METHOD_GET) assert.Equal(t, requestMethodGet, convertRequestMethod(&event)) }) t.Run("request method head", func(t *testing.T) { event := newEvent(auditV1.AttributeContext_HTTP_METHOD_HEAD) assert.Equal(t, requestMethodHead, convertRequestMethod(&event)) }) t.Run("request method post", func(t *testing.T) { event := newEvent(auditV1.AttributeContext_HTTP_METHOD_POST) assert.Equal(t, requestMethodPost, convertRequestMethod(&event)) }) t.Run("request method put", func(t *testing.T) { event := newEvent(auditV1.AttributeContext_HTTP_METHOD_PUT) assert.Equal(t, requestMethodPut, convertRequestMethod(&event)) }) t.Run("request method delete", func(t *testing.T) { event := newEvent(auditV1.AttributeContext_HTTP_METHOD_DELETE) assert.Equal(t, requestMethodDelete, convertRequestMethod(&event)) }) t.Run("request method connect", func(t *testing.T) { event := newEvent(auditV1.AttributeContext_HTTP_METHOD_CONNECT) assert.Equal(t, requestMethodConnect, convertRequestMethod(&event)) }) t.Run("request method options", func(t *testing.T) { event := newEvent(auditV1.AttributeContext_HTTP_METHOD_OPTIONS) assert.Equal(t, requestMethodOptions, convertRequestMethod(&event)) }) t.Run("request method trace", func(t *testing.T) { event := newEvent(auditV1.AttributeContext_HTTP_METHOD_TRACE) assert.Equal(t, requestMethodTrace, convertRequestMethod(&event)) }) t.Run("request method patch", func(t *testing.T) { event := newEvent(auditV1.AttributeContext_HTTP_METHOD_PATCH) assert.Equal(t, requestMethodPatch, convertRequestMethod(&event)) }) t.Run("request method other", func(t *testing.T) { event := newEvent(auditV1.AttributeContext_HTTP_METHOD_OTHER) assert.Equal(t, requestMethodOther, convertRequestMethod(&event)) }) t.Run("request method unspecified", func(t *testing.T) { event := newEvent(auditV1.AttributeContext_HTTP_METHOD_UNSPECIFIED) assert.Equal(t, requestMethodUnspecified, convertRequestMethod(&event)) }) } func Test_IdFromRequestAttributes(t *testing.T) { t.Run("id from value", func(t *testing.T) { rawId := "d2bcd43e-8418-462b-89a5-64db1af4aca1/1" result := IdFromRequestAttributes(&auditV1.AuditLogEntry{ProtoPayload: &auditV1.AuditLog{ RequestMetadata: &auditV1.RequestMetadata{ RequestAttributes: &auditV1.AttributeContext_Request{ Id: &rawId, }, }, }}) assert.Equal(t, "d2bcd43e-8418-462b-89a5-64db1af4aca1", result) _, err := uuid.Parse(result) assert.NoError(t, err) }) t.Run("invalid format", func(t *testing.T) { rawId := "d2bcd43e-8418-462b-89a5-64db1af4aca1:1" result := IdFromRequestAttributes(&auditV1.AuditLogEntry{ProtoPayload: &auditV1.AuditLog{ RequestMetadata: &auditV1.RequestMetadata{ RequestAttributes: &auditV1.AttributeContext_Request{ Id: &rawId, }, }, }}) assert.NotEqual(t, "d2bcd43e-8418-462b-89a5-64db1af4aca1", result) _, err := uuid.Parse(result) assert.NoError(t, err) }) t.Run("empty", func(t *testing.T) { rawId := "" result := IdFromRequestAttributes(&auditV1.AuditLogEntry{ProtoPayload: &auditV1.AuditLog{ RequestMetadata: &auditV1.RequestMetadata{ RequestAttributes: &auditV1.AttributeContext_Request{ Id: &rawId, }, }, }}) assert.NotEqual(t, "", result) _, err := uuid.Parse(result) assert.NoError(t, err) }) t.Run("not set", func(t *testing.T) { result := IdFromRequestAttributes(&auditV1.AuditLogEntry{ProtoPayload: &auditV1.AuditLog{ RequestMetadata: &auditV1.RequestMetadata{ RequestAttributes: &auditV1.AttributeContext_Request{}, }, }}) assert.NotEqual(t, "", result) _, err := uuid.Parse(result) assert.NoError(t, err) }) } func Test_convertToOpenTelemetryLogFormat(t *testing.T) { validator, err := protovalidate.New() assert.NoError(t, err) validationProcessor := NewValidationProcessor(validator) t.Run("public organization event", func(t *testing.T) { event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil) routableIdentifier := pkgAuditCommon.NewRoutableIdentifier(objectIdentifier) routableEvent, err := internalAuditApi.ValidateAndSerializePartially(validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, routableIdentifier) assert.NoError(t, err) record, err := convertToOpenTelemetryFormat("workerId", "eu01", event, routableEvent) assert.NoError(t, err) assert.Equal(t, event.Timestamp.AsTime(), record.Timestamp()) assert.Equal(t, event.Timestamp.AsTime(), record.ObservedTimestamp()) assert.Equal(t, otelLog.StringValue(event.ProtoPayload.OperationName), record.Body()) assert.Equal(t, otelLog.SeverityInfo, record.Severity()) assert.Equal(t, severityTextInfo, record.SeverityText()) attributeConstraints := NewAttributesConstraints( newConstraint("service.name", "resource-manager"), newConstraint("service.instance.id", "workerId"), newConstraint("cloud.region", "eu01"), newConstraint("stackit.resource.type", "ORGANIZATION"), newConstraint("stackit.resource.id", objectIdentifier.Identifier), newConstraint("stackit.log.id", IdFromRequestAttributes(event)), newConstraint("stackit.log.type", "AUDIT"), newConstraint("stackit.action", "resourcemanager.organization.created"), newConstraint("stackit.visibility", "PUBLIC"), newConstraint("stackit.initiator", event.ProtoPayload.AuthenticationInfo.PrincipalId), newConstraint("user_agent.original", event.ProtoPayload.RequestMetadata.CallerSuppliedUserAgent), newConstraint("http.request.method", convertRequestMethod(event)), newConstraint("client.address", event.ProtoPayload.RequestMetadata.CallerIp), newConstraint("server.address", event.ProtoPayload.RequestMetadata.RequestAttributes.Host), newConstraint("url.path", event.ProtoPayload.RequestMetadata.RequestAttributes.Path), newConstraint("stackit.response.status", "200"), newConstraint("logname", event.LogName), newConstraint("insertid", event.InsertId), newConstraint("correlationid", *event.CorrelationId), newConstraint("stackit.event.time", event.Timestamp.AsTime().Format(time.RFC3339)), newConstraint("stackit.request.time", event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime().Format(time.RFC3339)), newConstraint("stackit.response.time", event.ProtoPayload.ResponseMetadata.ResponseAttributes.Time.AsTime().Format(time.RFC3339)), newConstraint("stackit.initiator.email", *event.ProtoPayload.AuthenticationInfo.PrincipalEmail), newConstraint("stackit.request.body", "{}"), ).WithSliceConstraints(labelsAsConstraints(event.Labels)...) record.WalkAttributes(attributeConstraints.assertAttributesFunc(t)) attributeConstraints.assertAllAttributesMatched(t) assert.NoError(t, validationProcessor.OnEmit(context.Background(), convertLogRecordToSdkRecord(record, trace.SpanFromContext(context.Background())))) }) t.Run("private organization event", func(t *testing.T) { event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil) routableIdentifier := pkgAuditCommon.NewRoutableIdentifier(objectIdentifier) routableEvent, err := internalAuditApi.ValidateAndSerializePartially(validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, routableIdentifier) assert.NoError(t, err) record, err := convertToOpenTelemetryFormat("workerId", "eu01", event, routableEvent) assert.NoError(t, err) assert.Equal(t, event.Timestamp.AsTime(), record.Timestamp()) assert.Equal(t, event.Timestamp.AsTime(), record.ObservedTimestamp()) assert.Equal(t, otelLog.StringValue(event.ProtoPayload.OperationName), record.Body()) assert.Equal(t, otelLog.SeverityInfo, record.Severity()) assert.Equal(t, severityTextInfo, record.SeverityText()) attributeConstraints := NewAttributesConstraints( newConstraint("service.name", "resource-manager"), newConstraint("service.instance.id", "workerId"), newConstraint("cloud.region", "eu01"), newConstraint("stackit.resource.type", "ORGANIZATION"), newConstraint("stackit.resource.id", objectIdentifier.Identifier), newConstraint("stackit.log.id", IdFromRequestAttributes(event)), newConstraint("stackit.log.type", "AUDIT"), newConstraint("stackit.action", "resourcemanager.organization.created"), newConstraint("stackit.visibility", "INTERNAL"), newConstraint("stackit.initiator", event.ProtoPayload.AuthenticationInfo.PrincipalId), newConstraint("user_agent.original", event.ProtoPayload.RequestMetadata.CallerSuppliedUserAgent), newConstraint("http.request.method", convertRequestMethod(event)), newConstraint("client.address", event.ProtoPayload.RequestMetadata.CallerIp), newConstraint("server.address", event.ProtoPayload.RequestMetadata.RequestAttributes.Host), newConstraint("url.path", event.ProtoPayload.RequestMetadata.RequestAttributes.Path), newConstraint("stackit.response.status", "200"), newConstraint("logname", event.LogName), newConstraint("insertid", event.InsertId), newConstraint("correlationid", *event.CorrelationId), newConstraint("stackit.event.time", event.Timestamp.AsTime().Format(time.RFC3339)), newConstraint("stackit.request.time", event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime().Format(time.RFC3339)), newConstraint("stackit.response.time", event.ProtoPayload.ResponseMetadata.ResponseAttributes.Time.AsTime().Format(time.RFC3339)), newConstraint("stackit.initiator.email", *event.ProtoPayload.AuthenticationInfo.PrincipalEmail), newConstraint("stackit.request.body", "{}"), ).WithSliceConstraints(labelsAsConstraints(event.Labels)...) record.WalkAttributes(attributeConstraints.assertAttributesFunc(t)) attributeConstraints.assertAllAttributesMatched(t) assert.NoError(t, validationProcessor.OnEmit(context.Background(), convertLogRecordToSdkRecord(record, trace.SpanFromContext(context.Background())))) }) t.Run("public folder event", func(t *testing.T) { event, objectIdentifier := internalAuditApi.NewFolderAuditEvent(nil) routableIdentifier := pkgAuditCommon.NewRoutableIdentifier(objectIdentifier) routableEvent, err := internalAuditApi.ValidateAndSerializePartially(validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, routableIdentifier) assert.NoError(t, err) record, err := convertToOpenTelemetryFormat("workerId", "eu01", event, routableEvent) assert.NoError(t, err) assert.Equal(t, event.Timestamp.AsTime(), record.Timestamp()) assert.Equal(t, event.Timestamp.AsTime(), record.ObservedTimestamp()) assert.Equal(t, otelLog.StringValue(event.ProtoPayload.OperationName), record.Body()) assert.Equal(t, otelLog.SeverityInfo, record.Severity()) assert.Equal(t, severityTextInfo, record.SeverityText()) attributeConstraints := NewAttributesConstraints( newConstraint("service.name", "resource-manager"), newConstraint("service.instance.id", "workerId"), newConstraint("cloud.region", "eu01"), newConstraint("stackit.resource.type", "FOLDER"), newConstraint("stackit.resource.id", objectIdentifier.Identifier), newConstraint("stackit.log.id", IdFromRequestAttributes(event)), newConstraint("stackit.log.type", "AUDIT"), newConstraint("stackit.action", "resourcemanager.folder.created"), newConstraint("stackit.visibility", "PUBLIC"), newConstraint("stackit.initiator", event.ProtoPayload.AuthenticationInfo.PrincipalId), newConstraint("user_agent.original", event.ProtoPayload.RequestMetadata.CallerSuppliedUserAgent), newConstraint("http.request.method", convertRequestMethod(event)), newConstraint("client.address", event.ProtoPayload.RequestMetadata.CallerIp), newConstraint("server.address", event.ProtoPayload.RequestMetadata.RequestAttributes.Host), newConstraint("url.path", event.ProtoPayload.RequestMetadata.RequestAttributes.Path), newConstraint("stackit.response.status", "200"), newConstraint("logname", event.LogName), newConstraint("insertid", event.InsertId), newConstraint("correlationid", *event.CorrelationId), newConstraint("stackit.event.time", event.Timestamp.AsTime().Format(time.RFC3339)), newConstraint("stackit.request.time", event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime().Format(time.RFC3339)), newConstraint("stackit.response.time", event.ProtoPayload.ResponseMetadata.ResponseAttributes.Time.AsTime().Format(time.RFC3339)), newConstraint("stackit.initiator.email", *event.ProtoPayload.AuthenticationInfo.PrincipalEmail), newConstraint("stackit.request.body", "{}"), ).WithSliceConstraints(labelsAsConstraints(event.Labels)...) record.WalkAttributes(attributeConstraints.assertAttributesFunc(t)) attributeConstraints.assertAllAttributesMatched(t) assert.NoError(t, validationProcessor.OnEmit(context.Background(), convertLogRecordToSdkRecord(record, trace.SpanFromContext(context.Background())))) }) t.Run("private folder event", func(t *testing.T) { event, objectIdentifier := internalAuditApi.NewFolderAuditEvent(nil) routableIdentifier := pkgAuditCommon.NewRoutableIdentifier(objectIdentifier) routableEvent, err := internalAuditApi.ValidateAndSerializePartially(validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, routableIdentifier) assert.NoError(t, err) record, err := convertToOpenTelemetryFormat("workerId", "eu01", event, routableEvent) assert.NoError(t, err) assert.Equal(t, event.Timestamp.AsTime(), record.Timestamp()) assert.Equal(t, event.Timestamp.AsTime(), record.ObservedTimestamp()) assert.Equal(t, otelLog.StringValue(event.ProtoPayload.OperationName), record.Body()) assert.Equal(t, otelLog.SeverityInfo, record.Severity()) assert.Equal(t, severityTextInfo, record.SeverityText()) attributeConstraints := NewAttributesConstraints( newConstraint("service.name", "resource-manager"), newConstraint("service.instance.id", "workerId"), newConstraint("cloud.region", "eu01"), newConstraint("stackit.resource.type", "FOLDER"), newConstraint("stackit.resource.id", objectIdentifier.Identifier), newConstraint("stackit.log.id", IdFromRequestAttributes(event)), newConstraint("stackit.log.type", "AUDIT"), newConstraint("stackit.action", "resourcemanager.folder.created"), newConstraint("stackit.visibility", "INTERNAL"), newConstraint("stackit.initiator", event.ProtoPayload.AuthenticationInfo.PrincipalId), newConstraint("user_agent.original", event.ProtoPayload.RequestMetadata.CallerSuppliedUserAgent), newConstraint("http.request.method", convertRequestMethod(event)), newConstraint("client.address", event.ProtoPayload.RequestMetadata.CallerIp), newConstraint("server.address", event.ProtoPayload.RequestMetadata.RequestAttributes.Host), newConstraint("url.path", event.ProtoPayload.RequestMetadata.RequestAttributes.Path), newConstraint("stackit.response.status", "200"), newConstraint("logname", event.LogName), newConstraint("insertid", event.InsertId), newConstraint("correlationid", *event.CorrelationId), newConstraint("stackit.event.time", event.Timestamp.AsTime().Format(time.RFC3339)), newConstraint("stackit.request.time", event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime().Format(time.RFC3339)), newConstraint("stackit.response.time", event.ProtoPayload.ResponseMetadata.ResponseAttributes.Time.AsTime().Format(time.RFC3339)), newConstraint("stackit.initiator.email", *event.ProtoPayload.AuthenticationInfo.PrincipalEmail), newConstraint("stackit.request.body", "{}"), ).WithSliceConstraints(labelsAsConstraints(event.Labels)...) record.WalkAttributes(attributeConstraints.assertAttributesFunc(t)) attributeConstraints.assertAllAttributesMatched(t) assert.NoError(t, validationProcessor.OnEmit(context.Background(), convertLogRecordToSdkRecord(record, trace.SpanFromContext(context.Background())))) }) t.Run("public project event", func(t *testing.T) { event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(nil) routableIdentifier := pkgAuditCommon.NewRoutableIdentifier(objectIdentifier) routableEvent, err := internalAuditApi.ValidateAndSerializePartially(validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, routableIdentifier) assert.NoError(t, err) record, err := convertToOpenTelemetryFormat("workerId", "eu01", event, routableEvent) assert.NoError(t, err) assert.Equal(t, event.Timestamp.AsTime(), record.Timestamp()) assert.Equal(t, event.Timestamp.AsTime(), record.ObservedTimestamp()) assert.Equal(t, otelLog.StringValue(event.ProtoPayload.OperationName), record.Body()) assert.Equal(t, otelLog.SeverityInfo, record.Severity()) assert.Equal(t, severityTextInfo, record.SeverityText()) attributeConstraints := NewAttributesConstraints( newConstraint("service.name", "resource-manager"), newConstraint("service.instance.id", "workerId"), newConstraint("cloud.region", "eu01"), newConstraint("stackit.resource.type", "PROJECT"), newConstraint("stackit.resource.id", objectIdentifier.Identifier), newConstraint("stackit.log.id", IdFromRequestAttributes(event)), newConstraint("stackit.log.type", "AUDIT"), newConstraint("stackit.action", "resourcemanager.project.created"), newConstraint("stackit.visibility", "PUBLIC"), newConstraint("stackit.initiator", event.ProtoPayload.AuthenticationInfo.PrincipalId), newConstraint("user_agent.original", event.ProtoPayload.RequestMetadata.CallerSuppliedUserAgent), newConstraint("http.request.method", convertRequestMethod(event)), newConstraint("client.address", event.ProtoPayload.RequestMetadata.CallerIp), newConstraint("server.address", event.ProtoPayload.RequestMetadata.RequestAttributes.Host), newConstraint("url.path", event.ProtoPayload.RequestMetadata.RequestAttributes.Path), newConstraint("stackit.response.status", "200"), newConstraint("logname", event.LogName), newConstraint("insertid", event.InsertId), newConstraint("correlationid", *event.CorrelationId), newConstraint("stackit.event.time", event.Timestamp.AsTime().Format(time.RFC3339)), newConstraint("stackit.request.time", event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime().Format(time.RFC3339)), newConstraint("stackit.response.time", event.ProtoPayload.ResponseMetadata.ResponseAttributes.Time.AsTime().Format(time.RFC3339)), newConstraint("stackit.initiator.email", *event.ProtoPayload.AuthenticationInfo.PrincipalEmail), newConstraint("stackit.request.body", "{}"), ).WithSliceConstraints(labelsAsConstraints(event.Labels)...) record.WalkAttributes(attributeConstraints.assertAttributesFunc(t)) attributeConstraints.assertAllAttributesMatched(t) assert.NoError(t, validationProcessor.OnEmit(context.Background(), convertLogRecordToSdkRecord(record, trace.SpanFromContext(context.Background())))) }) t.Run("private project event", func(t *testing.T) { event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(nil) routableIdentifier := pkgAuditCommon.NewRoutableIdentifier(objectIdentifier) routableEvent, err := internalAuditApi.ValidateAndSerializePartially(validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, routableIdentifier) assert.NoError(t, err) record, err := convertToOpenTelemetryFormat("workerId", "eu01", event, routableEvent) assert.NoError(t, err) assert.Equal(t, event.Timestamp.AsTime(), record.Timestamp()) assert.Equal(t, event.Timestamp.AsTime(), record.ObservedTimestamp()) assert.Equal(t, otelLog.StringValue(event.ProtoPayload.OperationName), record.Body()) assert.Equal(t, otelLog.SeverityInfo, record.Severity()) assert.Equal(t, severityTextInfo, record.SeverityText()) attributeConstraints := NewAttributesConstraints( newConstraint("service.name", "resource-manager"), newConstraint("service.instance.id", "workerId"), newConstraint("cloud.region", "eu01"), newConstraint("stackit.resource.type", "PROJECT"), newConstraint("stackit.resource.id", objectIdentifier.Identifier), newConstraint("stackit.log.id", IdFromRequestAttributes(event)), newConstraint("stackit.log.type", "AUDIT"), newConstraint("stackit.action", "resourcemanager.project.created"), newConstraint("stackit.visibility", "INTERNAL"), newConstraint("stackit.initiator", event.ProtoPayload.AuthenticationInfo.PrincipalId), newConstraint("user_agent.original", event.ProtoPayload.RequestMetadata.CallerSuppliedUserAgent), newConstraint("http.request.method", convertRequestMethod(event)), newConstraint("client.address", event.ProtoPayload.RequestMetadata.CallerIp), newConstraint("server.address", event.ProtoPayload.RequestMetadata.RequestAttributes.Host), newConstraint("url.path", event.ProtoPayload.RequestMetadata.RequestAttributes.Path), newConstraint("stackit.response.status", "200"), newConstraint("logname", event.LogName), newConstraint("insertid", event.InsertId), newConstraint("correlationid", *event.CorrelationId), newConstraint("stackit.event.time", event.Timestamp.AsTime().Format(time.RFC3339)), newConstraint("stackit.request.time", event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime().Format(time.RFC3339)), newConstraint("stackit.response.time", event.ProtoPayload.ResponseMetadata.ResponseAttributes.Time.AsTime().Format(time.RFC3339)), newConstraint("stackit.initiator.email", *event.ProtoPayload.AuthenticationInfo.PrincipalEmail), newConstraint("stackit.request.body", "{}"), ).WithSliceConstraints(labelsAsConstraints(event.Labels)...) record.WalkAttributes(attributeConstraints.assertAttributesFunc(t)) attributeConstraints.assertAllAttributesMatched(t) assert.NoError(t, validationProcessor.OnEmit(context.Background(), convertLogRecordToSdkRecord(record, trace.SpanFromContext(context.Background())))) }) t.Run("private project system event", func(t *testing.T) { event := internalAuditApi.NewProjectSystemAuditEvent(nil) routableIdentifier := pkgAuditCommon.RoutableSystemIdentifier routableEvent, err := internalAuditApi.ValidateAndSerializePartially(validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, routableIdentifier) assert.NoError(t, err) record, err := convertToOpenTelemetryFormat("workerId", "eu01", event, routableEvent) assert.NoError(t, err) assert.Equal(t, event.Timestamp.AsTime(), record.Timestamp()) assert.Equal(t, event.Timestamp.AsTime(), record.ObservedTimestamp()) assert.Equal(t, otelLog.StringValue(event.ProtoPayload.OperationName), record.Body()) assert.Equal(t, otelLog.SeverityInfo, record.Severity()) assert.Equal(t, severityTextInfo, record.SeverityText()) attributeConstraints := NewAttributesConstraints( newConstraint("service.name", "resource-manager"), newConstraint("service.instance.id", "workerId"), newConstraint("cloud.region", "eu01"), newConstraint("stackit.resource.type", "SYSTEM"), newConstraint("stackit.resource.id", routableIdentifier.Identifier), newConstraint("stackit.log.id", IdFromRequestAttributes(event)), newConstraint("stackit.log.type", "AUDIT"), newConstraint("stackit.action", "resourcemanager.system.changed"), newConstraint("stackit.visibility", "INTERNAL"), newConstraint("stackit.initiator", event.ProtoPayload.AuthenticationInfo.PrincipalId), newConstraint("user_agent.original", event.ProtoPayload.RequestMetadata.CallerSuppliedUserAgent), newConstraint("http.request.method", convertRequestMethod(event)), newConstraint("client.address", event.ProtoPayload.RequestMetadata.CallerIp), newConstraint("server.address", event.ProtoPayload.RequestMetadata.RequestAttributes.Host), newConstraint("url.path", event.ProtoPayload.RequestMetadata.RequestAttributes.Path), newConstraint("stackit.response.status", "200"), newConstraint("logname", event.LogName), newConstraint("insertid", event.InsertId), newConstraint("correlationid", *event.CorrelationId), newConstraint("stackit.event.time", event.Timestamp.AsTime().Format(time.RFC3339)), newConstraint("stackit.request.time", event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime().Format(time.RFC3339)), newConstraint("stackit.response.time", event.ProtoPayload.ResponseMetadata.ResponseAttributes.Time.AsTime().Format(time.RFC3339)), newConstraint("stackit.initiator.email", *event.ProtoPayload.AuthenticationInfo.PrincipalEmail), newConstraint("stackit.initiator.serviceaccountname", *event.ProtoPayload.AuthenticationInfo.ServiceAccountName), newConstraint("stackit.initiator.serviceaccount0.id", "system"), newConstraint("stackit.request.body", "{}"), ).WithSliceConstraints(labelsAsConstraints(event.Labels)...) record.WalkAttributes(attributeConstraints.assertAttributesFunc(t)) attributeConstraints.assertAllAttributesMatched(t) assert.NoError(t, validationProcessor.OnEmit(context.Background(), convertLogRecordToSdkRecord(record, trace.SpanFromContext(context.Background())))) }) t.Run("private system event", func(t *testing.T) { event := internalAuditApi.NewSystemAuditEvent(nil) routableIdentifier := pkgAuditCommon.RoutableSystemIdentifier routableEvent, err := internalAuditApi.ValidateAndSerializePartially(validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, routableIdentifier) assert.NoError(t, err) record, err := convertToOpenTelemetryFormat("workerId", "eu01", event, routableEvent) assert.NoError(t, err) assert.Equal(t, event.Timestamp.AsTime(), record.Timestamp()) assert.Equal(t, event.Timestamp.AsTime(), record.ObservedTimestamp()) assert.Equal(t, otelLog.StringValue(event.ProtoPayload.OperationName), record.Body()) assert.Equal(t, otelLog.SeverityInfo, record.Severity()) assert.Equal(t, severityTextInfo, record.SeverityText()) attributeConstraints := NewAttributesConstraints( newConstraint("service.name", "resource-manager"), newConstraint("service.instance.id", "workerId"), newConstraint("cloud.region", "eu01"), newConstraint("stackit.resource.type", "SYSTEM"), newConstraint("stackit.resource.id", routableIdentifier.Identifier), newConstraint("stackit.log.id", IdFromRequestAttributes(event)), newConstraint("stackit.log.type", "AUDIT"), newConstraint("stackit.action", "resourcemanager.system.changed"), newConstraint("stackit.visibility", "INTERNAL"), newConstraint("stackit.initiator", event.ProtoPayload.AuthenticationInfo.PrincipalId), newConstraint("user_agent.original", event.ProtoPayload.RequestMetadata.CallerSuppliedUserAgent), newConstraint("http.request.method", convertRequestMethod(event)), newConstraint("client.address", event.ProtoPayload.RequestMetadata.CallerIp), newConstraint("server.address", event.ProtoPayload.RequestMetadata.RequestAttributes.Host), newConstraint("url.path", event.ProtoPayload.RequestMetadata.RequestAttributes.Path), newConstraint("stackit.response.status", "200"), newConstraint("logname", event.LogName), newConstraint("insertid", event.InsertId), newConstraint("correlationid", *event.CorrelationId), newConstraint("stackit.event.time", event.Timestamp.AsTime().Format(time.RFC3339)), newConstraint("stackit.request.time", event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime().Format(time.RFC3339)), newConstraint("stackit.response.time", event.ProtoPayload.ResponseMetadata.ResponseAttributes.Time.AsTime().Format(time.RFC3339)), newConstraint("stackit.initiator.email", *event.ProtoPayload.AuthenticationInfo.PrincipalEmail), newConstraint("stackit.initiator.serviceaccountname", *event.ProtoPayload.AuthenticationInfo.ServiceAccountName), newConstraint("stackit.initiator.serviceaccount0.id", "system"), newConstraint("stackit.request.body", "{}"), ).WithSliceConstraints(labelsAsConstraints(event.Labels)...) record.WalkAttributes(attributeConstraints.assertAttributesFunc(t)) attributeConstraints.assertAllAttributesMatched(t) assert.NoError(t, validationProcessor.OnEmit(context.Background(), convertLogRecordToSdkRecord(record, trace.SpanFromContext(context.Background())))) }) t.Run("private system event with service account delegation", func(t *testing.T) { serviceAccountPrincipalId := uuid.NewString() serviceAccountPrincipalEmail := "test@example.com" updateServiceAccount := func(e *auditV1.AuditLogEntry) { e.ProtoPayload.AuthenticationInfo.ServiceAccountDelegationInfo[0].Authority = &auditV1.ServiceAccountDelegationInfo_IdpPrincipal_{ IdpPrincipal: &auditV1.ServiceAccountDelegationInfo_IdpPrincipal{ PrincipalId: serviceAccountPrincipalId, PrincipalEmail: serviceAccountPrincipalEmail, }, } } event := internalAuditApi.NewSystemAuditEvent(&updateServiceAccount) routableIdentifier := pkgAuditCommon.RoutableSystemIdentifier routableEvent, err := internalAuditApi.ValidateAndSerializePartially(validator, event, auditV1.Visibility_VISIBILITY_PRIVATE, routableIdentifier) assert.NoError(t, err) record, err := convertToOpenTelemetryFormat("workerId", "eu01", event, routableEvent) assert.NoError(t, err) assert.Equal(t, event.Timestamp.AsTime(), record.Timestamp()) assert.Equal(t, event.Timestamp.AsTime(), record.ObservedTimestamp()) assert.Equal(t, otelLog.StringValue(event.ProtoPayload.OperationName), record.Body()) assert.Equal(t, otelLog.SeverityInfo, record.Severity()) assert.Equal(t, severityTextInfo, record.SeverityText()) attributeConstraints := NewAttributesConstraints( newConstraint("service.name", "resource-manager"), newConstraint("service.instance.id", "workerId"), newConstraint("cloud.region", "eu01"), newConstraint("stackit.resource.type", "SYSTEM"), newConstraint("stackit.resource.id", routableIdentifier.Identifier), newConstraint("stackit.log.id", IdFromRequestAttributes(event)), newConstraint("stackit.log.type", "AUDIT"), newConstraint("stackit.action", "resourcemanager.system.changed"), newConstraint("stackit.visibility", "INTERNAL"), newConstraint("stackit.initiator", event.ProtoPayload.AuthenticationInfo.PrincipalId), newConstraint("user_agent.original", event.ProtoPayload.RequestMetadata.CallerSuppliedUserAgent), newConstraint("http.request.method", convertRequestMethod(event)), newConstraint("client.address", event.ProtoPayload.RequestMetadata.CallerIp), newConstraint("server.address", event.ProtoPayload.RequestMetadata.RequestAttributes.Host), newConstraint("url.path", event.ProtoPayload.RequestMetadata.RequestAttributes.Path), newConstraint("stackit.response.status", "200"), newConstraint("logname", event.LogName), newConstraint("insertid", event.InsertId), newConstraint("correlationid", *event.CorrelationId), newConstraint("stackit.event.time", event.Timestamp.AsTime().Format(time.RFC3339)), newConstraint("stackit.request.time", event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime().Format(time.RFC3339)), newConstraint("stackit.response.time", event.ProtoPayload.ResponseMetadata.ResponseAttributes.Time.AsTime().Format(time.RFC3339)), newConstraint("stackit.initiator.email", *event.ProtoPayload.AuthenticationInfo.PrincipalEmail), newConstraint("stackit.initiator.serviceaccountname", *event.ProtoPayload.AuthenticationInfo.ServiceAccountName), newConstraint("stackit.initiator.serviceaccount0.id", serviceAccountPrincipalId), newConstraint("stackit.initiator.serviceaccount0.email", serviceAccountPrincipalEmail), newConstraint("stackit.request.body", "{}"), ).WithSliceConstraints(labelsAsConstraints(event.Labels)...) record.WalkAttributes(attributeConstraints.assertAttributesFunc(t)) attributeConstraints.assertAllAttributesMatched(t) assert.NoError(t, validationProcessor.OnEmit(context.Background(), convertLogRecordToSdkRecord(record, trace.SpanFromContext(context.Background())))) }) t.Run("with query params", func(t *testing.T) { event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil) escapedQuery := url.QueryEscape("param=value") event.ProtoPayload.RequestMetadata.RequestAttributes.Query = &escapedQuery routableIdentifier := pkgAuditCommon.NewRoutableIdentifier(objectIdentifier) routableEvent, err := internalAuditApi.ValidateAndSerializePartially(validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, routableIdentifier) assert.NoError(t, err) record, err := convertToOpenTelemetryFormat("workerId", "eu01", event, routableEvent) assert.NoError(t, err) assert.Equal(t, event.Timestamp.AsTime(), record.Timestamp()) assert.Equal(t, event.Timestamp.AsTime(), record.ObservedTimestamp()) assert.Equal(t, otelLog.StringValue(event.ProtoPayload.OperationName), record.Body()) assert.Equal(t, otelLog.SeverityInfo, record.Severity()) assert.Equal(t, severityTextInfo, record.SeverityText()) attributeConstraints := NewAttributesConstraints( newConstraint("service.name", "resource-manager"), newConstraint("service.instance.id", "workerId"), newConstraint("cloud.region", "eu01"), newConstraint("stackit.resource.type", "ORGANIZATION"), newConstraint("stackit.resource.id", objectIdentifier.Identifier), newConstraint("stackit.log.id", IdFromRequestAttributes(event)), newConstraint("stackit.log.type", "AUDIT"), newConstraint("stackit.action", "resourcemanager.organization.created"), newConstraint("stackit.visibility", "PUBLIC"), newConstraint("stackit.initiator", event.ProtoPayload.AuthenticationInfo.PrincipalId), newConstraint("user_agent.original", event.ProtoPayload.RequestMetadata.CallerSuppliedUserAgent), newConstraint("http.request.method", convertRequestMethod(event)), newConstraint("client.address", event.ProtoPayload.RequestMetadata.CallerIp), newConstraint("server.address", event.ProtoPayload.RequestMetadata.RequestAttributes.Host), newConstraint("url.path", event.ProtoPayload.RequestMetadata.RequestAttributes.Path), newConstraint("url.query", "param%3Dvalue"), newConstraint("stackit.response.status", "200"), newConstraint("logname", event.LogName), newConstraint("insertid", event.InsertId), newConstraint("correlationid", *event.CorrelationId), newConstraint("stackit.event.time", event.Timestamp.AsTime().Format(time.RFC3339)), newConstraint("stackit.request.time", event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime().Format(time.RFC3339)), newConstraint("stackit.response.time", event.ProtoPayload.ResponseMetadata.ResponseAttributes.Time.AsTime().Format(time.RFC3339)), newConstraint("stackit.initiator.email", *event.ProtoPayload.AuthenticationInfo.PrincipalEmail), newConstraint("stackit.request.body", "{}"), ).WithSliceConstraints(labelsAsConstraints(event.Labels)...) record.WalkAttributes(attributeConstraints.assertAttributesFunc(t)) attributeConstraints.assertAllAttributesMatched(t) assert.NoError(t, validationProcessor.OnEmit(context.Background(), convertLogRecordToSdkRecord(record, trace.SpanFromContext(context.Background())))) }) t.Run("with request body", func(t *testing.T) { event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil) routableIdentifier := pkgAuditCommon.NewRoutableIdentifier(objectIdentifier) request := map[string]any{"key": "value"} requestStruct, err := structpb.NewStruct(request) assert.NoError(t, err) event.ProtoPayload.Request = requestStruct routableEvent, err := internalAuditApi.ValidateAndSerializePartially(validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, routableIdentifier) assert.NoError(t, err) record, err := convertToOpenTelemetryFormat("workerId", "eu01", event, routableEvent) assert.NoError(t, err) assert.Equal(t, event.Timestamp.AsTime(), record.Timestamp()) assert.Equal(t, event.Timestamp.AsTime(), record.ObservedTimestamp()) assert.Equal(t, otelLog.StringValue(event.ProtoPayload.OperationName), record.Body()) assert.Equal(t, otelLog.SeverityInfo, record.Severity()) assert.Equal(t, severityTextInfo, record.SeverityText()) attributeConstraints := NewAttributesConstraints( newConstraint("service.name", "resource-manager"), newConstraint("service.instance.id", "workerId"), newConstraint("cloud.region", "eu01"), newConstraint("stackit.resource.type", "ORGANIZATION"), newConstraint("stackit.resource.id", objectIdentifier.Identifier), newConstraint("stackit.log.id", IdFromRequestAttributes(event)), newConstraint("stackit.log.type", "AUDIT"), newConstraint("stackit.action", "resourcemanager.organization.created"), newConstraint("stackit.visibility", "PUBLIC"), newConstraint("stackit.initiator", event.ProtoPayload.AuthenticationInfo.PrincipalId), newConstraint("user_agent.original", event.ProtoPayload.RequestMetadata.CallerSuppliedUserAgent), newConstraint("http.request.method", convertRequestMethod(event)), newConstraint("client.address", event.ProtoPayload.RequestMetadata.CallerIp), newConstraint("server.address", event.ProtoPayload.RequestMetadata.RequestAttributes.Host), newConstraint("url.path", event.ProtoPayload.RequestMetadata.RequestAttributes.Path), newConstraint("stackit.response.status", "200"), newConstraint("logname", event.LogName), newConstraint("insertid", event.InsertId), newConstraint("correlationid", *event.CorrelationId), newConstraint("stackit.event.time", event.Timestamp.AsTime().Format(time.RFC3339)), newConstraint("stackit.request.time", event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime().Format(time.RFC3339)), newConstraint("stackit.response.time", event.ProtoPayload.ResponseMetadata.ResponseAttributes.Time.AsTime().Format(time.RFC3339)), newConstraint("stackit.initiator.email", *event.ProtoPayload.AuthenticationInfo.PrincipalEmail), newConstraint("stackit.request.body", "{\"key\":\"value\"}"), ).WithSliceConstraints(labelsAsConstraints(event.Labels)...) record.WalkAttributes(attributeConstraints.assertAttributesFunc(t)) attributeConstraints.assertAllAttributesMatched(t) assert.NoError(t, validationProcessor.OnEmit(context.Background(), convertLogRecordToSdkRecord(record, trace.SpanFromContext(context.Background())))) }) t.Run("with response body", func(t *testing.T) { event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil) event.ProtoPayload.ResponseMetadata.ResponseAttributes.Size = wrapperspb.Int64(1000) event.ProtoPayload.ResponseMetadata.ResponseAttributes.NumResponseItems = wrapperspb.Int64(1) response := map[string]any{"key": "value"} responseStruct, err := structpb.NewStruct(response) assert.NoError(t, err) event.ProtoPayload.Response = responseStruct routableIdentifier := pkgAuditCommon.NewRoutableIdentifier(objectIdentifier) routableEvent, err := internalAuditApi.ValidateAndSerializePartially(validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, routableIdentifier) assert.NoError(t, err) record, err := convertToOpenTelemetryFormat("workerId", "eu01", event, routableEvent) assert.NoError(t, err) assert.Equal(t, event.Timestamp.AsTime(), record.Timestamp()) assert.Equal(t, event.Timestamp.AsTime(), record.ObservedTimestamp()) assert.Equal(t, otelLog.StringValue(event.ProtoPayload.OperationName), record.Body()) assert.Equal(t, otelLog.SeverityInfo, record.Severity()) assert.Equal(t, severityTextInfo, record.SeverityText()) attributeConstraints := NewAttributesConstraints( newConstraint("service.name", "resource-manager"), newConstraint("service.instance.id", "workerId"), newConstraint("cloud.region", "eu01"), newConstraint("stackit.resource.type", "ORGANIZATION"), newConstraint("stackit.resource.id", objectIdentifier.Identifier), newConstraint("stackit.log.id", IdFromRequestAttributes(event)), newConstraint("stackit.log.type", "AUDIT"), newConstraint("stackit.action", "resourcemanager.organization.created"), newConstraint("stackit.visibility", "PUBLIC"), newConstraint("stackit.initiator", event.ProtoPayload.AuthenticationInfo.PrincipalId), newConstraint("user_agent.original", event.ProtoPayload.RequestMetadata.CallerSuppliedUserAgent), newConstraint("http.request.method", convertRequestMethod(event)), newConstraint("client.address", event.ProtoPayload.RequestMetadata.CallerIp), newConstraint("server.address", event.ProtoPayload.RequestMetadata.RequestAttributes.Host), newConstraint("url.path", event.ProtoPayload.RequestMetadata.RequestAttributes.Path), newConstraint("stackit.response.status", "200"), newConstraint("logname", event.LogName), newConstraint("insertid", event.InsertId), newConstraint("correlationid", *event.CorrelationId), newConstraint("stackit.event.time", event.Timestamp.AsTime().Format(time.RFC3339)), newConstraint("stackit.request.time", event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime().Format(time.RFC3339)), newConstraint("stackit.response.time", event.ProtoPayload.ResponseMetadata.ResponseAttributes.Time.AsTime().Format(time.RFC3339)), newConstraint("stackit.initiator.email", *event.ProtoPayload.AuthenticationInfo.PrincipalEmail), newConstraint("stackit.request.body", "{}"), newConstraint("stackit.response.size", "1000"), newConstraint("stackit.response.items.count", "1"), newConstraint("stackit.response.body", "{\"key\":\"value\"}"), ).WithSliceConstraints(labelsAsConstraints(event.Labels)...) record.WalkAttributes(attributeConstraints.assertAttributesFunc(t)) attributeConstraints.assertAllAttributesMatched(t) assert.NoError(t, validationProcessor.OnEmit(context.Background(), convertLogRecordToSdkRecord(record, trace.SpanFromContext(context.Background())))) }) t.Run("with error response", func(t *testing.T) { event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil) routableIdentifier := pkgAuditCommon.NewRoutableIdentifier(objectIdentifier) responseErrorMessage := "error message" event.ProtoPayload.ResponseMetadata.ErrorMessage = &responseErrorMessage errorMap := map[string]any{"key": "value1"} errorStruct, err := structpb.NewStruct(errorMap) assert.NoError(t, err) event.ProtoPayload.ResponseMetadata.ErrorDetails = []*structpb.Struct{errorStruct} event.ProtoPayload.ResponseMetadata.StatusCode = wrapperspb.Int32(int32(400)) routableEvent, err := internalAuditApi.ValidateAndSerializePartially(validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, routableIdentifier) assert.NoError(t, err) record, err := convertToOpenTelemetryFormat("workerId", "eu01", event, routableEvent) assert.NoError(t, err) assert.Equal(t, event.Timestamp.AsTime(), record.Timestamp()) assert.Equal(t, event.Timestamp.AsTime(), record.ObservedTimestamp()) assert.Equal(t, otelLog.StringValue(event.ProtoPayload.OperationName), record.Body()) assert.Equal(t, otelLog.SeverityInfo, record.Severity()) assert.Equal(t, severityTextInfo, record.SeverityText()) attributeConstraints := NewAttributesConstraints( newConstraint("service.name", "resource-manager"), newConstraint("service.instance.id", "workerId"), newConstraint("cloud.region", "eu01"), newConstraint("stackit.resource.type", "ORGANIZATION"), newConstraint("stackit.resource.id", objectIdentifier.Identifier), newConstraint("stackit.log.id", IdFromRequestAttributes(event)), newConstraint("stackit.log.type", "AUDIT"), newConstraint("stackit.action", "resourcemanager.organization.created"), newConstraint("stackit.visibility", "PUBLIC"), newConstraint("stackit.initiator", event.ProtoPayload.AuthenticationInfo.PrincipalId), newConstraint("user_agent.original", event.ProtoPayload.RequestMetadata.CallerSuppliedUserAgent), newConstraint("http.request.method", convertRequestMethod(event)), newConstraint("client.address", event.ProtoPayload.RequestMetadata.CallerIp), newConstraint("server.address", event.ProtoPayload.RequestMetadata.RequestAttributes.Host), newConstraint("url.path", event.ProtoPayload.RequestMetadata.RequestAttributes.Path), newConstraint("stackit.response.status", "400"), newConstraint("logname", event.LogName), newConstraint("insertid", event.InsertId), newConstraint("correlationid", *event.CorrelationId), newConstraint("stackit.event.time", event.Timestamp.AsTime().Format(time.RFC3339)), newConstraint("stackit.request.time", event.ProtoPayload.RequestMetadata.RequestAttributes.Time.AsTime().Format(time.RFC3339)), newConstraint("stackit.response.time", event.ProtoPayload.ResponseMetadata.ResponseAttributes.Time.AsTime().Format(time.RFC3339)), newConstraint("stackit.initiator.email", *event.ProtoPayload.AuthenticationInfo.PrincipalEmail), newConstraint("stackit.request.body", "{}"), newConstraint("stackit.response.error.message", *event.ProtoPayload.ResponseMetadata.ErrorMessage), newConstraint("stackit.response.error.details0", "{\"key\":\"value1\"}"), ).WithSliceConstraints(labelsAsConstraints(event.Labels)...) record.WalkAttributes(attributeConstraints.assertAttributesFunc(t)) attributeConstraints.assertAllAttributesMatched(t) assert.NoError(t, validationProcessor.OnEmit(context.Background(), convertLogRecordToSdkRecord(record, trace.SpanFromContext(context.Background())))) }) } //nolint:gosec // SeverityNumber is not that large func Test_convertSdkRecordToLogRecord(t *testing.T) { // prepare record record := otelSdkLog.Record{} initRequiredPrivateRecordFields(&record) span := trace.SpanFromContext(context.Background()) eventTime := time.Now() eventObservedTime := time.Now() record.SetTimestamp(eventTime) record.SetObservedTimestamp(eventObservedTime) record.SetSeverity(otelLog.SeverityError) record.SetSeverityText("ERROR") record.SetBody(otelLog.StringValue("body")) record.SetEventName("eventName") record.SetTraceID(span.SpanContext().TraceID()) record.SetSpanID(span.SpanContext().SpanID()) record.SetTraceFlags(span.SpanContext().TraceFlags()) otlpAttrs := make([]otelLog.KeyValue, 6) otlpAttrs[0] = otelLog.KeyValue{Key: "key1", Value: otelLog.StringValue("value1")} otlpAttrs[1] = otelLog.KeyValue{Key: "key2", Value: otelLog.BoolValue(true)} otlpAttrs[2] = otelLog.KeyValue{Key: "key3", Value: otelLog.IntValue(1000)} otlpAttrs[3] = otelLog.KeyValue{Key: "key4", Value: otelLog.Float64Value(1.0)} otlpAttrs[4] = otelLog.KeyValue{Key: "key5", Value: otelLog.BytesValue([]byte("bytes"))} otlpAttrs[5] = otelLog.KeyValue{Key: "key6", Value: otelLog.SliceValue( otelLog.StringValue("value1"), otelLog.StringValue("value2"), )} record.AddAttributes(otlpAttrs...) // convert convertedRecord := convertSdkRecordToLogRecord(&record, span) assert.Equal(t, uint64(eventTime.UnixNano()), convertedRecord.TimeUnixNano) assert.Equal(t, uint64(eventObservedTime.UnixNano()), convertedRecord.ObservedTimeUnixNano) assert.Equal(t, otlpLogsV1.SeverityNumber(record.Severity()), convertedRecord.SeverityNumber) assert.Equal(t, record.SeverityText(), convertedRecord.SeverityText) assert.Equal(t, &otlpCommonV1.AnyValue_StringValue{StringValue: otelLog.StringValue("body").AsString()}, convertedRecord.Body.Value) assert.Equal(t, record.EventName(), convertedRecord.EventName) assert.Equal(t, []byte(record.TraceID().String()), convertedRecord.TraceId) assert.Equal(t, []byte(record.SpanID().String()), convertedRecord.SpanId) assert.Equal(t, uint32(record.TraceFlags()), convertedRecord.Flags) assert.Len(t, convertedRecord.Attributes, 6) assert.Equal(t, otlpAttrs[0].Key, convertedRecord.Attributes[0].Key) assert.Equal(t, otlpAttrs[0].Value.AsString(), convertedRecord.Attributes[0].Value.GetStringValue()) assert.Equal(t, otlpAttrs[1].Key, convertedRecord.Attributes[1].Key) assert.Equal(t, otlpAttrs[1].Value.AsBool(), convertedRecord.Attributes[1].Value.GetBoolValue()) assert.Equal(t, otlpAttrs[2].Key, convertedRecord.Attributes[2].Key) assert.Equal(t, otlpAttrs[2].Value.AsInt64(), convertedRecord.Attributes[2].Value.GetIntValue()) assert.Equal(t, otlpAttrs[3].Key, convertedRecord.Attributes[3].Key) assert.Equal(t, otlpAttrs[3].Value.AsFloat64(), convertedRecord.Attributes[3].Value.GetDoubleValue()) assert.Equal(t, otlpAttrs[4].Key, convertedRecord.Attributes[4].Key) assert.Equal(t, otlpAttrs[4].Value.AsBytes(), convertedRecord.Attributes[4].Value.GetBytesValue()) assert.Equal(t, otlpAttrs[5].Key, convertedRecord.Attributes[5].Key) assert.Len(t, otlpAttrs[5].Value.AsSlice(), 2) assert.Len(t, (*convertedRecord.Attributes[5].Value.GetArrayValue()).Values, 2) assert.Equal(t, otlpAttrs[5].Value.AsSlice()[0].AsString(), convertedRecord.Attributes[5].Value.GetArrayValue().Values[0].GetStringValue()) assert.Equal(t, otlpAttrs[5].Value.AsSlice()[1].AsString(), convertedRecord.Attributes[5].Value.GetArrayValue().Values[1].GetStringValue()) } func Test_ValidationProcessor(t *testing.T) { validator, err := protovalidate.New() assert.NoError(t, err) validationProcessor := NewValidationProcessor(validator) t.Run("enabled", func(t *testing.T) { assert.True(t, validationProcessor.Enabled(context.Background(), otelSdkLog.EnabledParameters{})) }) t.Run("shutdown", func(t *testing.T) { assert.NoError(t, validationProcessor.Shutdown(context.Background())) }) t.Run("force flush", func(t *testing.T) { assert.NoError(t, validationProcessor.ForceFlush(context.Background())) }) t.Run("on emit", func(t *testing.T) { validateEvent := func(event *auditV1.AuditLogEntry, objectIdentifier *auditV1.ObjectIdentifier, visibility auditV1.Visibility) { routableIdentifier := pkgAuditCommon.NewRoutableIdentifier(objectIdentifier) routableEvent, err := internalAuditApi.ValidateAndSerializePartially(validator, event, visibility, routableIdentifier) assert.NoError(t, err) record, err := convertToOpenTelemetryFormat("workerId", "eu01", event, routableEvent) assert.NoError(t, err) span := trace.SpanFromContext(context.Background()) sdkRecord := convertLogRecordToSdkRecord(record, span) assert.NoError(t, validationProcessor.OnEmit(context.Background(), sdkRecord)) } t.Run("public organization event", func(t *testing.T) { event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil) validateEvent(event, objectIdentifier, auditV1.Visibility_VISIBILITY_PUBLIC) }) t.Run("private organization event", func(t *testing.T) { event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil) validateEvent(event, objectIdentifier, auditV1.Visibility_VISIBILITY_PRIVATE) }) t.Run("public folder event", func(t *testing.T) { event, objectIdentifier := internalAuditApi.NewFolderAuditEvent(nil) validateEvent(event, objectIdentifier, auditV1.Visibility_VISIBILITY_PUBLIC) }) t.Run("private folder event", func(t *testing.T) { event, objectIdentifier := internalAuditApi.NewFolderAuditEvent(nil) validateEvent(event, objectIdentifier, auditV1.Visibility_VISIBILITY_PRIVATE) }) t.Run("public project event", func(t *testing.T) { event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(nil) validateEvent(event, objectIdentifier, auditV1.Visibility_VISIBILITY_PUBLIC) }) t.Run("private project event", func(t *testing.T) { event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(nil) validateEvent(event, objectIdentifier, auditV1.Visibility_VISIBILITY_PRIVATE) }) t.Run("system event", func(t *testing.T) { event := internalAuditApi.NewSystemAuditEvent(nil) validateEvent(event, pkgAuditCommon.SystemIdentifier, auditV1.Visibility_VISIBILITY_PRIVATE) }) t.Run("invalid event", func(t *testing.T) { event, objectIdentifier := internalAuditApi.NewProjectAuditEvent(nil) routableIdentifier := pkgAuditCommon.NewRoutableIdentifier(objectIdentifier) routableEvent, err := internalAuditApi.ValidateAndSerializePartially(validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, routableIdentifier) assert.NoError(t, err) record, err := convertToOpenTelemetryFormat("workerId", "invalid", event, routableEvent) assert.NoError(t, err) span := trace.SpanFromContext(context.Background()) sdkRecord := convertLogRecordToSdkRecord(record, span) assert.EqualError(t, validationProcessor.OnEmit(context.Background(), sdkRecord), "validation error: cloud.region attribute is required and should be global or eu01, eu02, ...") }) }) } func Test_TelemetryHubLoggerPool(t *testing.T) { pool, err := NewTelemetryHubLoggerPool(2, NewMockTelemetryHubLoggerOption()) assert.NoError(t, err) logger := pool.Get() assert.NotNil(t, logger) logger2 := pool.Get() assert.NotNil(t, logger2) assert.NotEqual(t, logger, logger2) logger3 := pool.Get() assert.NotNil(t, logger3) assert.Equal(t, logger, logger3) logger4 := pool.Get() assert.NotNil(t, logger4) assert.Equal(t, logger2, logger4) } func Test_synchronousExporter(t *testing.T) { validator, err := protovalidate.New() assert.NoError(t, err) // prepare exporter innerExporter := newMockExporter() exporter := NewSynchronousExporter(innerExporter) t.Run("export", func(t *testing.T) { innerExporter.Reset() // prepare event event, objectIdentifier := internalAuditApi.NewOrganizationAuditEvent(nil) routableIdentifier := pkgAuditCommon.NewRoutableIdentifier(objectIdentifier) routableEvent, err := internalAuditApi.ValidateAndSerializePartially(validator, event, auditV1.Visibility_VISIBILITY_PUBLIC, routableIdentifier) assert.NoError(t, err) // convert to record record, err := convertToOpenTelemetryFormat("workerId", "eu01", event, routableEvent) assert.NoError(t, err) span := trace.SpanFromContext(context.Background()) sdkRecord := convertLogRecordToSdkRecord(record, span) // export assert.NoError(t, exporter.Export(context.Background(), []otelSdkLog.Record{*sdkRecord})) // check result assert.False(t, innerExporter.forceFlushInvoked) assert.False(t, innerExporter.shutdownInvoked) records := innerExporter.records assert.Len(t, records, 1) assert.Equal(t, *sdkRecord, records[0]) }) t.Run("shutdown", func(t *testing.T) { innerExporter.Reset() // invoke assert.NoError(t, exporter.Shutdown(context.Background())) // assert assert.True(t, innerExporter.shutdownInvoked) assert.False(t, innerExporter.forceFlushInvoked) }) t.Run("force flush", func(t *testing.T) { innerExporter.Reset() // invoke assert.NoError(t, exporter.ForceFlush(context.Background())) // assert assert.True(t, innerExporter.forceFlushInvoked) assert.False(t, innerExporter.shutdownInvoked) }) } type mockExporter struct { records []otelSdkLog.Record shutdownInvoked bool forceFlushInvoked bool } var _ otelSdkLog.Exporter = (*mockExporter)(nil) func newMockExporter() *mockExporter { return &mockExporter{} } func (e *mockExporter) Export(_ context.Context, records []otelSdkLog.Record) error { e.records = records return nil } func (e *mockExporter) Shutdown(_ context.Context) error { e.shutdownInvoked = true return nil } func (e *mockExporter) ForceFlush(_ context.Context) error { e.forceFlushInvoked = true return nil } func (e *mockExporter) Reset() { e.records = nil e.shutdownInvoked = false e.forceFlushInvoked = false } type AttributesConstraints struct { constraintMap map[string]string } func NewAttributesConstraints(constraints ...constraint) *AttributesConstraints { constraintMap := make(map[string]string) for _, constraint := range constraints { constraintMap[constraint.key] = constraint.value } return &AttributesConstraints{ constraintMap: constraintMap, } } func (c *AttributesConstraints) WithSliceConstraints(constraints ...constraint) *AttributesConstraints { for _, constraint := range constraints { c.constraintMap[constraint.key] = constraint.value } return c } func (c *AttributesConstraints) assertAttributesIfSpecifiedFunc(t *testing.T) func(value otelLog.KeyValue) bool { return func(value otelLog.KeyValue) bool { expectedValue, hasKey := c.constraintMap[value.Key] if hasKey { assert.Equal(t, expectedValue, value.Value.String()) delete(c.constraintMap, value.Key) } return true } } func (c *AttributesConstraints) assertAttributesFunc(t *testing.T) func(value otelLog.KeyValue) bool { return func(value otelLog.KeyValue) bool { expectedValue, hasKey := c.constraintMap[value.Key] assert.Truef(t, hasKey, "unexpected attribute '%s': %s", value.Key, value.Value) assert.Equal(t, expectedValue, value.Value.String()) delete(c.constraintMap, value.Key) return true } } func (c *AttributesConstraints) assertAllAttributesMatched(t *testing.T) { if len(c.constraintMap) == 0 { return } for k, v := range c.constraintMap { println(fmt.Sprintf("Error: missing attribute %s: %s", k, v)) } assert.FailNow(t, "number of constraints larger than given validated list of attributes") } func labelsAsConstraints(labels map[string]string) []constraint { constraints := make([]constraint, 0, 0) for k, v := range labels { constraints = append(constraints, newConstraint(fmt.Sprintf("labels.%s", k), v)) } return constraints } type constraint struct { key, value string } func newConstraint(key, value string) constraint { return constraint{key, value} }