package telemetry import ( "encoding/json" "testing" otellog "go.opentelemetry.io/otel/log" sdklog "go.opentelemetry.io/otel/sdk/log" ) func TestMapSeverity(t *testing.T) { tests := []struct { input string want otellog.Severity }{ {"trace", otellog.SeverityTrace}, {"debug", otellog.SeverityDebug}, {"info", otellog.SeverityInfo}, {"warn", otellog.SeverityWarn}, {"warning", otellog.SeverityWarn}, {"error", otellog.SeverityError}, {"fatal", otellog.SeverityFatal}, {"panic", otellog.SeverityFatal2}, {"unknown", otellog.SeverityInfo}, {"", otellog.SeverityInfo}, {"INFO", otellog.SeverityInfo}, // uppercase falls to default {"PANIC", otellog.SeverityInfo}, // uppercase falls to default {"gibberish", otellog.SeverityInfo}, } for _, tc := range tests { t.Run("level_"+tc.input, func(t *testing.T) { got := mapSeverity(tc.input) if got != tc.want { t.Errorf("mapSeverity(%q) = %v, want %v", tc.input, got, tc.want) } }) } } func newTestBridge(t *testing.T) *LogBridge { t.Helper() provider := sdklog.NewLoggerProvider() t.Cleanup(func() { _ = provider.Shutdown(t.Context()) }) return &LogBridge{provider: provider} } func TestLogBridgeWrite(t *testing.T) { tests := []struct { name string input interface{} // will be marshaled to JSON; use string for raw input raw string // if non-empty, use this directly instead of marshaling input }{ { name: "valid_json_with_message_level_and_extras", input: map[string]interface{}{ "message": "request handled", "level": "info", "method": "GET", "status": float64(200), }, }, { name: "message_only_no_level", input: map[string]interface{}{ "message": "hello world", }, }, { name: "level_only_no_message", input: map[string]interface{}{ "level": "error", }, }, { name: "empty_json_object", input: map[string]interface{}{}, }, { name: "string_float64_bool_attributes", input: map[string]interface{}{ "message": "test", "level": "debug", "str_val": "hello", "num_val": float64(3.14), "bool_val": true, }, }, { name: "complex_nested_object_attribute", input: map[string]interface{}{ "message": "nested", "level": "warn", "nested": map[string]interface{}{"foo": "bar", "n": float64(1)}, }, }, { name: "time_field_skipped_in_attributes", input: map[string]interface{}{ "message": "with time", "level": "info", "time": "2025-01-01T00:00:00Z", "extra": "kept", }, }, { name: "malformed_json", raw: "this is not json at all", }, { name: "malformed_json_partial", raw: `{"broken":`, }, { name: "array_attribute_marshaled_as_string", input: map[string]interface{}{ "message": "arrays", "tags": []interface{}{"a", "b"}, }, }, { name: "null_value_attribute", input: map[string]interface{}{ "message": "nulls", "val": nil, }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { bridge := newTestBridge(t) var p []byte if tc.raw != "" { p = []byte(tc.raw) } else { var err error p, err = json.Marshal(tc.input) if err != nil { t.Fatalf("failed to marshal test input: %v", err) } } n, err := bridge.Write(p) if n != len(p) { t.Errorf("Write() returned n=%d, want %d", n, len(p)) } if err != nil { t.Errorf("Write() returned err=%v, want nil", err) } }) } } func TestLogBridgeWriteAlwaysReturnsLenAndNil(t *testing.T) { bridge := newTestBridge(t) inputs := [][]byte{ []byte(`{"message":"ok","level":"info"}`), []byte(`not json`), []byte(`{}`), []byte(``), []byte(`[]`), } for _, p := range inputs { n, err := bridge.Write(p) if n != len(p) { t.Errorf("Write(%q) n=%d, want %d", string(p), n, len(p)) } if err != nil { t.Errorf("Write(%q) err=%v, want nil", string(p), err) } } }