3
3
import mock
4
4
import pytest
5
5
6
- from ddtrace .internal .logger import DDLogger
6
+ import ddtrace .internal .logger
7
7
from ddtrace .internal .logger import get_logger
8
+ from ddtrace .internal .logger import LoggingBucket
8
9
from tests .utils import BaseTestCase
9
10
10
11
11
12
ALL_LEVEL_NAMES = ("debug" , "info" , "warning" , "error" , "exception" , "critical" , "fatal" )
12
13
13
14
14
- class DDLoggerTestCase (BaseTestCase ):
15
+ class LoggerTestCase (BaseTestCase ):
15
16
def setUp (self ):
16
- super (DDLoggerTestCase , self ).setUp ()
17
+ super (LoggerTestCase , self ).setUp ()
17
18
18
19
self .root = logging .root
19
20
self .manager = self .root .manager
@@ -26,7 +27,11 @@ def tearDown(self):
26
27
self .root = None
27
28
self .manager = None
28
29
29
- super (DDLoggerTestCase , self ).tearDown ()
30
+ # Reset to default values
31
+ ddtrace .internal .logger ._buckets .clear ()
32
+ ddtrace .internal .logger ._rate_limit = 60
33
+
34
+ super (LoggerTestCase , self ).tearDown ()
30
35
31
36
def _make_record (
32
37
self ,
@@ -42,42 +47,31 @@ def _make_record(
42
47
):
43
48
return logger .makeRecord (logger .name , level , fn , lno , msg , args , exc_info , func , extra )
44
49
45
- @mock .patch ("ddtrace.internal.logger.DDLogger.handle" )
46
- def assert_log_records (self , log , expected_levels , handle ):
47
- for name in ALL_LEVEL_NAMES :
48
- method = getattr (log , name )
49
- method ("test" )
50
-
51
- records = [args [0 ][0 ] for args in handle .call_args_list ]
52
- for record in records :
53
- self .assertIsInstance (record , logging .LogRecord )
54
- self .assertTrue ("test.logger" in record .name or "ddtrace" in record .name )
55
-
56
- levels = [r .levelname for r in records ]
57
- self .assertEqual (levels , expected_levels )
58
-
59
50
def test_get_logger (self ):
60
51
"""
61
52
When using `get_logger` to get a logger
62
53
When the logger does not exist
63
- We create a new DDLogger
54
+ We create a new logging.Logger
64
55
When the logger exists
65
56
We return the expected logger
66
57
When a different logger is requested
67
- We return a new DDLogger
58
+ We return a new logging.Logger
68
59
When a Placeholder exists
69
- We return DDLogger
60
+ We return logging.Logger
70
61
"""
62
+ assert self .manager is not None
63
+
71
64
# Assert the logger doesn't already exist
72
65
self .assertNotIn ("test.logger" , self .manager .loggerDict )
73
66
74
67
# Fetch a new logger
75
68
log = get_logger ("test.logger" )
69
+ assert ddtrace .internal .logger .log_filter in log .filters
76
70
self .assertEqual (log .name , "test.logger" )
77
71
self .assertEqual (log .level , logging .NOTSET )
78
72
79
- # Ensure it is a DDLogger
80
- self .assertIsInstance (log , DDLogger )
73
+ # Ensure it is a logging.Logger
74
+ self .assertIsInstance (log , logging . Logger )
81
75
# Make sure it is stored in all the places we expect
82
76
self .assertEqual (self .manager .getLogger ("test.logger" ), log )
83
77
self .assertEqual (self .manager .loggerDict ["test.logger" ], log )
@@ -93,163 +87,45 @@ def test_get_logger(self):
93
87
self .assertNotEqual (log , new_log )
94
88
95
89
# If a PlaceHolder is in place of the logger
96
- # We should return the DDLogger
90
+ # We should return the logging.Logger
97
91
self .assertIsInstance (self .manager .loggerDict ["new.test" ], logging .PlaceHolder )
98
92
log = get_logger ("new.test" )
99
93
self .assertEqual (log .name , "new.test" )
100
- self .assertIsInstance (log , DDLogger )
101
-
102
- def test_get_logger_children (self ):
103
- """
104
- When using `get_logger` to get a logger
105
- We appropriately assign children loggers
106
-
107
- DEV: This test case is to ensure we are calling `manager._fixupChildren(logger)`
108
- """
109
- root = get_logger ("test" )
110
- root .setLevel (logging .WARNING )
111
-
112
- child_logger = get_logger ("test.newplaceholder.long.component" )
113
- self .assertEqual (child_logger .parent , root )
114
-
115
- parent_logger = get_logger ("test.newplaceholder" )
116
- self .assertEqual (child_logger .parent , parent_logger )
117
-
118
- parent_logger .setLevel (logging .INFO )
119
- # Because the child logger's level remains unset, it should inherit
120
- # the level of its closest parent, which is INFO.
121
- # If we did not properly maintain the logger tree, this would fail
122
- # because child_logger would be set to the default when it was created
123
- # which was logging.WARNING.
124
- self .assertEqual (child_logger .getEffectiveLevel (), logging .INFO )
125
-
126
- # Clean up for future tests.
127
- root .setLevel (logging .NOTSET )
128
-
129
- def test_get_logger_parents (self ):
130
- """
131
- When using `get_logger` to get a logger
132
- We appropriately assign parent loggers
133
-
134
- DEV: This test case is to ensure we are calling `manager._fixupParents(logger)`
135
- """
136
- # Fetch a new logger
137
- test_log = get_logger ("test" )
138
- self .assertEqual (test_log .parent , self .root )
139
-
140
- # Fetch a new child log
141
- # Auto-associate with parent `test` logger
142
- child_log = get_logger ("test.child" )
143
- self .assertEqual (child_log .parent , test_log )
144
-
145
- # Deep child
146
- deep_log = get_logger ("test.child.logger.from.test.case" )
147
- self .assertEqual (deep_log .parent , child_log )
148
-
149
- def test_logger_init (self ):
150
- """
151
- When creating a new DDLogger
152
- Has the same interface as logging.Logger
153
- Configures a defaultdict for buckets
154
- Properly configures the rate limit
155
- """
156
- # Create a logger
157
- log = DDLogger ("test.logger" )
158
-
159
- # Ensure we set the name and use default log level
160
- self .assertEqual (log .name , "test.logger" )
161
- self .assertEqual (log .level , logging .NOTSET )
162
-
163
- # Assert DDLogger default properties
164
- self .assertIsInstance (log .buckets , dict )
165
- self .assertEqual (log .rate_limit , 60 )
166
-
167
- # Assert manager and parent
168
- # DEV: Parent is `None` because `manager._findParents()` doesn't get called
169
- # unless we use `get_logger` (this is the same behavior as `logging.getLogger` and `Logger('name')`)
170
- self .assertEqual (log .manager , self .manager )
171
- self .assertIsNone (log .parent )
172
-
173
- # Override rate limit from environment variable
174
- with self .override_env (dict (DD_TRACE_LOGGING_RATE = "10" )):
175
- log = DDLogger ("test.logger" )
176
- self .assertEqual (log .rate_limit , 10 )
177
-
178
- # Set specific log level
179
- log = DDLogger ("test.logger" , level = logging .DEBUG )
180
- self .assertEqual (log .level , logging .DEBUG )
181
-
182
- def test_logger_log (self ):
183
- """
184
- When calling `DDLogger` log methods
185
- We call `DDLogger.handle` with the expected log record
186
- """
187
- log = get_logger ("test.logger" )
188
-
189
- # -- NOTSET
190
- # By default no level is set so we only get warn, error, and critical messages
191
- self .assertEqual (log .level , logging .NOTSET )
192
- # `log.warning`, `log.error`, `log.exception`, `log.critical`, `log.fatal`
193
- self .assert_log_records (log , ["WARNING" , "ERROR" , "ERROR" , "CRITICAL" , "CRITICAL" ])
194
-
195
- # -- CRITICAL
196
- log .setLevel (logging .CRITICAL )
197
- # `log.critical`, `log.fatal`
198
- self .assert_log_records (log , ["CRITICAL" , "CRITICAL" ])
199
-
200
- # -- ERROR
201
- log .setLevel (logging .ERROR )
202
- # `log.error`, `log.exception`, `log.critical`, `log.fatal`
203
- self .assert_log_records (log , ["ERROR" , "ERROR" , "CRITICAL" , "CRITICAL" ])
204
-
205
- # -- WARN
206
- log .setLevel (logging .WARN )
207
- # `log.warning`, `log.error`, `log.exception`, `log.critical`, `log.fatal`
208
- self .assert_log_records (log , ["WARNING" , "ERROR" , "ERROR" , "CRITICAL" , "CRITICAL" ])
209
-
210
- # -- INFO
211
- log .setLevel (logging .INFO )
212
- # `log.info`, `log.warning`, `log.error`, `log.exception`, `log.critical`, `log.fatal`
213
- self .assert_log_records (log , ["INFO" , "WARNING" , "ERROR" , "ERROR" , "CRITICAL" , "CRITICAL" ])
214
-
215
- # -- DEBUG
216
- log .setLevel (logging .DEBUG )
217
- # `log.debug`, `log.info`, `log.warning`, `log.error`, `log.exception`, `log.critical`, `log.fatal`
218
- self .assert_log_records (log , ["DEBUG" , "INFO" , "WARNING" , "ERROR" , "ERROR" , "CRITICAL" , "CRITICAL" ])
94
+ self .assertIsInstance (log , logging .Logger )
219
95
220
- @mock .patch ("logging.Logger.handle " )
221
- def test_logger_handle_no_limit (self , base_handle ):
96
+ @mock .patch ("logging.Logger.callHandlers " )
97
+ def test_logger_handle_no_limit (self , call_handlers ):
222
98
"""
223
- Calling `DDLogger .handle`
99
+ Calling `logging.Logger .handle`
224
100
When no rate limit is set
225
101
Always calls the base `Logger.handle`
226
102
"""
227
103
# Configure an INFO logger with no rate limit
228
104
log = get_logger ("test.logger" )
229
105
log .setLevel (logging .INFO )
230
- log . rate_limit = 0
106
+ ddtrace . internal . logger . _rate_limit = 0
231
107
232
108
# Log a bunch of times very quickly (this is fast)
233
109
for _ in range (1000 ):
234
110
log .info ("test" )
235
111
236
112
# Assert that we did not perform any rate limiting
237
- self .assertEqual (base_handle .call_count , 1000 )
113
+ self .assertEqual (call_handlers .call_count , 1000 )
238
114
239
115
# Our buckets are empty
240
- self .assertEqual (log . buckets , dict ())
116
+ self .assertEqual (ddtrace . internal . logger . _buckets , dict ())
241
117
242
- @mock .patch ("logging.Logger.handle " )
243
- def test_logger_handle_debug (self , base_handle ):
118
+ @mock .patch ("logging.Logger.callHandlers " )
119
+ def test_logger_handle_debug (self , call_handlers ):
244
120
"""
245
- Calling `DDLogger .handle`
121
+ Calling `logging.Logger .handle`
246
122
When effective level is DEBUG
247
123
Always calls the base `Logger.handle`
248
124
"""
249
125
# Configure an INFO logger with no rate limit
250
126
log = get_logger ("test.logger" )
251
127
log .setLevel (logging .DEBUG )
252
- assert log . rate_limit > 0
128
+ assert ddtrace . internal . logger . _rate_limit > 0
253
129
254
130
# Log a bunch of times very quickly (this is fast)
255
131
for level in ALL_LEVEL_NAMES :
@@ -259,15 +135,15 @@ def test_logger_handle_debug(self, base_handle):
259
135
260
136
# Assert that we did not perform any rate limiting
261
137
total = 1000 * len (ALL_LEVEL_NAMES )
262
- self .assertTrue (total <= base_handle .call_count <= total + 1 )
138
+ self .assertTrue (total <= call_handlers .call_count <= total + 1 )
263
139
264
140
# Our buckets are empty
265
- self .assertEqual (log . buckets , dict ())
141
+ self .assertEqual (ddtrace . internal . logger . _buckets , dict ())
266
142
267
- @mock .patch ("logging.Logger.handle " )
268
- def test_logger_handle_bucket (self , base_handle ):
143
+ @mock .patch ("logging.Logger.callHandlers " )
144
+ def test_logger_handle_bucket (self , call_handlers ):
269
145
"""
270
- When calling `DDLogger .handle`
146
+ When calling `logging.Logger .handle`
271
147
With a record
272
148
We pass it to the base `Logger.handle`
273
149
We create a bucket for tracking
@@ -279,30 +155,31 @@ def test_logger_handle_bucket(self, base_handle):
279
155
log .handle (record )
280
156
281
157
# We passed to base Logger.handle
282
- base_handle .assert_called_once_with (record )
158
+ call_handlers .assert_called_once_with (record )
283
159
284
160
# We added an bucket entry for this record
285
161
key = (record .name , record .levelno , record .pathname , record .lineno )
286
- logging_bucket = log . buckets .get (key )
287
- self .assertIsInstance (logging_bucket , DDLogger . LoggingBucket )
162
+ logging_bucket = ddtrace . internal . logger . _buckets .get (key )
163
+ self .assertIsInstance (logging_bucket , LoggingBucket )
288
164
289
165
# The bucket entry is correct
290
- expected_bucket = int (record .created / log . rate_limit )
166
+ expected_bucket = int (record .created / ddtrace . internal . logger . _rate_limit )
291
167
self .assertEqual (logging_bucket .bucket , expected_bucket )
292
168
self .assertEqual (logging_bucket .skipped , 0 )
293
169
294
- @mock .patch ("logging.Logger.handle " )
295
- def test_logger_handle_bucket_limited (self , base_handle ):
170
+ @mock .patch ("logging.Logger.callHandlers " )
171
+ def test_logger_handle_bucket_limited (self , call_handlers ):
296
172
"""
297
- When calling `DDLogger .handle`
173
+ When calling `logging.Logger .handle`
298
174
With multiple records in a single time frame
299
175
We pass only the first to the base `Logger.handle`
300
176
We keep track of the number skipped
301
177
"""
302
178
log = get_logger ("test.logger" )
303
179
304
180
# Create log record and handle it
305
- first_record = self ._make_record (log , msg = "first" )
181
+ record = self ._make_record (log , msg = "first" )
182
+ first_record = record
306
183
log .handle (first_record )
307
184
308
185
for _ in range (100 ):
@@ -312,21 +189,22 @@ def test_logger_handle_bucket_limited(self, base_handle):
312
189
log .handle (record )
313
190
314
191
# We passed to base Logger.handle
315
- base_handle .assert_called_once_with (first_record )
192
+ call_handlers .assert_called_once_with (first_record )
316
193
317
194
# We added an bucket entry for these records
318
195
key = (record .name , record .levelno , record .pathname , record .lineno )
319
- logging_bucket = log .buckets .get (key )
196
+ logging_bucket = ddtrace .internal .logger ._buckets .get (key )
197
+ assert logging_bucket is not None
320
198
321
199
# The bucket entry is correct
322
- expected_bucket = int (first_record .created / log . rate_limit )
200
+ expected_bucket = int (first_record .created / ddtrace . internal . logger . _rate_limit )
323
201
self .assertEqual (logging_bucket .bucket , expected_bucket )
324
202
self .assertEqual (logging_bucket .skipped , 100 )
325
203
326
- @mock .patch ("logging.Logger.handle " )
327
- def test_logger_handle_bucket_skipped_msg (self , base_handle ):
204
+ @mock .patch ("logging.Logger.callHandlers " )
205
+ def test_logger_handle_bucket_skipped_msg (self , call_handlers ):
328
206
"""
329
- When calling `DDLogger .handle`
207
+ When calling `logging.Logger .handle`
330
208
When a bucket exists for a previous time frame
331
209
We pass only the record to the base `Logger.handle`
332
210
We update the record message to include the number of skipped messages
@@ -340,23 +218,23 @@ def test_logger_handle_bucket_skipped_msg(self, base_handle):
340
218
341
219
# Create a bucket entry for this record
342
220
key = (record .name , record .levelno , record .pathname , record .lineno )
343
- bucket = int (record .created / log . rate_limit )
221
+ bucket = int (record .created / ddtrace . internal . logger . _rate_limit )
344
222
# We want the time bucket to be for an older bucket
345
- log . buckets [key ] = DDLogger . LoggingBucket (bucket = bucket - 1 , skipped = 20 )
223
+ ddtrace . internal . logger . _buckets [key ] = LoggingBucket (bucket = bucket - 1 , skipped = 20 )
346
224
347
225
# Handle our record
348
226
log .handle (record )
349
227
350
228
# We passed to base Logger.handle
351
- base_handle .assert_called_once_with (record )
229
+ call_handlers .assert_called_once_with (record )
352
230
353
231
self .assertEqual (record .msg , original_msg + ", %s additional messages skipped" )
354
232
self .assertEqual (record .args , original_args + (20 ,))
355
233
self .assertEqual (record .getMessage (), "hello 1, 20 additional messages skipped" )
356
234
357
235
def test_logger_handle_bucket_key (self ):
358
236
"""
359
- When calling `DDLogger .handle`
237
+ When calling `logging.Logger .handle`
360
238
With different log messages
361
239
We use different buckets to limit them
362
240
"""
@@ -388,7 +266,7 @@ def get_key(record):
388
266
all_records = (record1 , record2 , record3 , record4 , record5 , record6 )
389
267
[log .handle (record ) for record in all_records ]
390
268
391
- buckets = log . buckets
269
+ buckets = ddtrace . internal . logger . _buckets
392
270
# We have 6 records but only end up with 5 buckets
393
271
self .assertEqual (len (buckets ), 5 )
394
272
0 commit comments