/ 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)