@@ -6,6 +6,7 @@ package logtest // import "go.opentelemetry.io/otel/log/logtest"
6
6
import (
7
7
"context"
8
8
"sync"
9
+ "time"
9
10
10
11
"go.opentelemetry.io/otel/attribute"
11
12
"go.opentelemetry.io/otel/log"
@@ -59,126 +60,175 @@ func NewRecorder(options ...Option) *Recorder {
59
60
}
60
61
}
61
62
62
- // ScopeRecords represents the records for a single instrumentation scope.
63
- type ScopeRecords struct {
64
- // Name is the name of the instrumentation scope.
63
+ // Recording represents the recorded log records snapshot.
64
+ type Recording map [Scope ][]Record
65
+
66
+ // Scope represents the instrumentation scope.
67
+ type Scope struct {
68
+ // Name is the name of the instrumentation scope. This should be the
69
+ // Go package name of that scope.
65
70
Name string
66
71
// Version is the version of the instrumentation scope.
67
72
Version string
68
73
// SchemaURL of the telemetry emitted by the scope.
69
74
SchemaURL string
70
75
// Attributes of the telemetry emitted by the scope.
71
76
Attributes attribute.Set
72
-
73
- // Records are the log records, and their associated context this
74
- // instrumentation scope recorded.
75
- Records []EmittedRecord
76
77
}
77
78
78
- // EmittedRecord holds a log record the instrumentation received, alongside its
79
- // context.
80
- type EmittedRecord struct {
81
- log.Record
82
-
83
- ctx context.Context
84
- }
79
+ // Record represents the record alongside its context.
80
+ type Record struct {
81
+ // Ensure forward compatibility by explicitly making this not comparable.
82
+ _ [0 ]func ()
85
83
86
- // Context provides the context emitted with the record.
87
- func (rwc EmittedRecord ) Context () context.Context {
88
- return rwc .ctx
84
+ Context context.Context
85
+ EventName string
86
+ Timestamp time.Time
87
+ ObservedTimestamp time.Time
88
+ Severity log.Severity
89
+ SeverityText string
90
+ Body log.Value
91
+ Attributes []log.KeyValue
89
92
}
90
93
91
- // Recorder is a recorder that stores all received log records
92
- // in-memory .
94
+ // Recorder stores all received log records in-memory.
95
+ // Recorder implements [log.LoggerProvider] .
93
96
type Recorder struct {
97
+ // Ensure forward compatibility by explicitly making this not comparable.
98
+ _ [0 ]func ()
99
+
94
100
embedded.LoggerProvider
95
101
96
102
mu sync.Mutex
97
- loggers [ ]* logger
103
+ loggers map [ Scope ]* logger
98
104
99
105
// enabledFn decides whether the recorder should enable logging of a record or not
100
106
enabledFn enabledFn
101
107
}
102
108
109
+ // Compile-time check Recorder implements log.LoggerProvider.
110
+ var _ log.LoggerProvider = (* Recorder )(nil )
111
+
112
+ // Clone returns a deep copy.
113
+ func (a Record ) Clone () Record {
114
+ b := a
115
+ attrs := make ([]log.KeyValue , len (a .Attributes ))
116
+ copy (attrs , a .Attributes )
117
+ b .Attributes = attrs
118
+ return b
119
+ }
120
+
103
121
// Logger returns a copy of Recorder as a [log.Logger] with the provided scope
104
122
// information.
105
123
func (r * Recorder ) Logger (name string , opts ... log.LoggerOption ) log.Logger {
106
124
cfg := log .NewLoggerConfig (opts ... )
107
-
108
- nl := & logger {
109
- scopeRecord : & ScopeRecords {
110
- Name : name ,
111
- Version : cfg .InstrumentationVersion (),
112
- SchemaURL : cfg .SchemaURL (),
113
- Attributes : cfg .InstrumentationAttributes (),
114
- },
115
- enabledFn : r .enabledFn ,
125
+ scope := Scope {
126
+ Name : name ,
127
+ Version : cfg .InstrumentationVersion (),
128
+ SchemaURL : cfg .SchemaURL (),
129
+ Attributes : cfg .InstrumentationAttributes (),
116
130
}
117
- r .addChildLogger (nl )
118
-
119
- return nl
120
- }
121
131
122
- func (r * Recorder ) addChildLogger (nl * logger ) {
123
132
r .mu .Lock ()
124
133
defer r .mu .Unlock ()
125
134
126
- r .loggers = append (r .loggers , nl )
135
+ if r .loggers == nil {
136
+ r .loggers = make (map [Scope ]* logger )
137
+ }
138
+
139
+ l , ok := r .loggers [scope ]
140
+ if ok {
141
+ return l
142
+ }
143
+ l = & logger {
144
+ enabledFn : r .enabledFn ,
145
+ }
146
+ r .loggers [scope ] = l
147
+ return l
127
148
}
128
149
129
- // Result returns the current in-memory recorder log records.
130
- func (r * Recorder ) Result () [] * ScopeRecords {
150
+ // Reset clears the in-memory log records for all loggers .
151
+ func (r * Recorder ) Reset () {
131
152
r .mu .Lock ()
132
153
defer r .mu .Unlock ()
133
154
134
- ret := []* ScopeRecords {}
135
155
for _ , l := range r .loggers {
136
- ret = append ( ret , l . scopeRecord )
156
+ l . Reset ( )
137
157
}
138
- return ret
139
158
}
140
159
141
- // Reset clears the in-memory log records for all loggers .
142
- func (r * Recorder ) Reset () {
160
+ // Result returns a deep copy of the current in-memory recorded log records.
161
+ func (r * Recorder ) Result () Recording {
143
162
r .mu .Lock ()
144
163
defer r .mu .Unlock ()
145
164
146
- for _ , l := range r .loggers {
147
- l .Reset ()
165
+ res := make (Recording , len (r .loggers ))
166
+ for s , l := range r .loggers {
167
+ func () {
168
+ l .mu .Lock ()
169
+ defer l .mu .Unlock ()
170
+ if l .records == nil {
171
+ res [s ] = nil
172
+ return
173
+ }
174
+ recs := make ([]Record , len (l .records ))
175
+ for i , r := range l .records {
176
+ recs [i ] = r .Clone ()
177
+ }
178
+ res [s ] = recs
179
+ }()
148
180
}
181
+ return res
149
182
}
150
183
151
184
type logger struct {
152
185
embedded.Logger
153
186
154
- mu sync.Mutex
155
- scopeRecord * ScopeRecords
187
+ mu sync.Mutex
188
+ records [] * Record
156
189
157
190
// enabledFn decides whether the recorder should enable logging of a record or not.
158
191
enabledFn enabledFn
159
192
}
160
193
161
194
// Enabled indicates whether a specific record should be stored.
162
- func (l * logger ) Enabled (ctx context.Context , opts log.EnabledParameters ) bool {
195
+ func (l * logger ) Enabled (ctx context.Context , param log.EnabledParameters ) bool {
163
196
if l .enabledFn == nil {
164
- return defaultEnabledFunc (ctx , opts )
197
+ return defaultEnabledFunc (ctx , param )
165
198
}
166
199
167
- return l .enabledFn (ctx , opts )
200
+ return l .enabledFn (ctx , param )
168
201
}
169
202
170
203
// Emit stores the log record.
171
204
func (l * logger ) Emit (ctx context.Context , record log.Record ) {
172
205
l .mu .Lock ()
173
206
defer l .mu .Unlock ()
174
207
175
- l .scopeRecord .Records = append (l .scopeRecord .Records , EmittedRecord {record , ctx })
208
+ attrs := make ([]log.KeyValue , 0 , record .AttributesLen ())
209
+ record .WalkAttributes (func (kv log.KeyValue ) bool {
210
+ attrs = append (attrs , kv )
211
+ return true
212
+ })
213
+
214
+ r := & Record {
215
+ Context : ctx ,
216
+ EventName : record .EventName (),
217
+ Timestamp : record .Timestamp (),
218
+ ObservedTimestamp : record .ObservedTimestamp (),
219
+ Severity : record .Severity (),
220
+ SeverityText : record .SeverityText (),
221
+ Body : record .Body (),
222
+ Attributes : attrs ,
223
+ }
224
+
225
+ l .records = append (l .records , r )
176
226
}
177
227
178
228
// Reset clears the in-memory log records.
179
229
func (l * logger ) Reset () {
180
230
l .mu .Lock ()
181
231
defer l .mu .Unlock ()
182
232
183
- l .scopeRecord . Records = nil
233
+ l .records = nil
184
234
}
0 commit comments