/ chronicles / dynamic_scope.nim
dynamic_scope.nim
1 import 2 std/typetraits, 3 stew/shims/macros, 4 ./[dynamic_scope_types, log_output, options, scope_helpers] 5 6 when runtimeFilteringEnabled: 7 import topics_registry 8 proc appenderIMPL[LogRecord: tuple, PropertyType]( 9 log: var LogRecord, 10 keyValuePair: ptr ScopeBindingBase[LogRecord], 11 enabled: SinksBitmask, 12 ) = 13 type ActualType = ptr ScopeBinding[LogRecord, PropertyType] 14 let v = ActualType(keyValuePair) 15 log.setProperty(v.name, v.value, enabled) 16 17 # Need to be specific with `not tuple` in the context that appenderIMPL is being 18 # used 19 proc appenderIMPL[LogRecord: not tuple, PropertyType]( 20 log: var LogRecord, keyValuePair: ptr ScopeBindingBase[LogRecord] 21 ) = 22 type ActualType = ptr ScopeBinding[LogRecord, PropertyType] 23 let v = ActualType(keyValuePair) 24 log.setProperty(v.name, v.value) 25 26 proc logAllDynamicProperties*[LogRecord: tuple]( 27 stream: typedesc, r: var LogRecord, enabled: SinksBitmask 28 ) = 29 # This proc is intended for internal use only 30 mixin tlsSlot 31 32 var frame = tlsSlot(stream) 33 while frame != nil: 34 for i in 0 ..< frame.bindingsCount: 35 let binding = frame.bindings[i] 36 when (NimMajor, NimMinor) >= (2, 2): 37 binding.appender(r, binding, enabled) 38 else: 39 cast[MultiLogAppender[LogRecord]](binding.appender)(r, binding, enabled) 40 frame = frame.prev 41 42 else: 43 proc appenderIMPL[LogRecord, PropertyType]( 44 log: var LogRecord, keyValuePair: ptr ScopeBindingBase[LogRecord] 45 ) = 46 type ActualType = ptr ScopeBinding[LogRecord, PropertyType] 47 let v = ActualType(keyValuePair) 48 log.setProperty(v.name, v.value) 49 50 proc logAllDynamicProperties*[LogRecord](stream: typedesc, r: var LogRecord) = 51 # This proc is intended for internal use only 52 mixin tlsSlot 53 54 var frame = tlsSlot(stream) 55 while frame != nil: 56 for i in 0 ..< frame.bindingsCount: 57 let binding = frame.bindings[i] 58 when (NimMajor, NimMinor) >= (2, 2): 59 binding.appender(r, binding) 60 else: 61 cast[LogAppender[LogRecord]](binding.appender)(r, binding) 62 frame = frame.prev 63 64 proc makeScopeBinding[T]( 65 LogRecord: typedesc, name: string, value: T 66 ): ScopeBinding[LogRecord, T] = 67 result.name = name 68 result.appender = appenderIMPL[LogRecord, T] 69 result.value = value 70 71 macro dynamicLogScopeIMPL*( 72 stream: typedesc, lexicalScopes: typed, args: varargs[untyped] 73 ): untyped = 74 # XXX: open question: should we support overriding of dynamic props 75 # inside inner scopes. This will have some run-time overhead. 76 let body = args[^1] 77 args.del(args.len - 1) 78 79 if body.kind != nnkStmtList: 80 error "dynamicLogScope expects a block", body 81 82 var 83 makeScopeBinding = bindSym"makeScopeBinding" 84 bindingsVars = newTree(nnkStmtList) 85 bindingsArray = newTree(nnkBracket) 86 bindingsArraySym = genSym(nskLet, "bindings") 87 RecordType = genSym(nskType, "Record") 88 89 for name, value in assignments(args, acLogStatement): 90 var bindingVar = genSym(nskLet, name) 91 92 bindingsVars.add quote do: 93 let `bindingVar` = `makeScopeBinding`(`RecordType`, `name`, `value`) 94 95 bindingsArray.add newCall("unsafeAddr", bindingVar) 96 97 when defined(js): 98 bindingsArray = prefix(bindingsArray, "@") 99 100 let totalBindingVars = bindingsVars.len 101 102 result = quote: 103 var prevBindingFrame = tlsSlot(`stream`) 104 105 try: 106 type `RecordType` = Record(`stream`) 107 # All of the dynamic binding pairs are placed on the stack. 108 `bindingsVars` 109 110 # An array is created to hold pointers to them. 111 # This works, because of the common base type `ScopeBindingBase[LogRecord]`. 112 let `bindingsArraySym` = `bindingsArray` 113 114 # A `BindingFrame` object is also placed on the stack, holding 115 # meta-data about the array and a link to the previous BindingFrame. 116 let bindingFrame = BindingsFrame[`RecordType`]( 117 prev: prevBindingFrame, 118 bindings: cast[BindingsArray[`RecordType`]](unsafeAddr `bindingsArraySym`), 119 bindingsCount: `totalBindingVars`, 120 ) 121 122 # The address of the new BindingFrame is written to a TLS location. 123 tlsSlot(`stream`) = unsafeAddr(bindingFrame) 124 125 # XXX: In resumable functions, we need help from the compiler to let us 126 # intercept yields and resumes so we can restore our context. 127 128 `body` 129 finally: 130 # After the scope block has been executed, we restore the previous 131 # top BindingFrame. 132 tlsSlot(`stream`) = prevBindingFrame 133 134 when defined(debugLogImpl): 135 echo repr(result)