tstructuredlogging.nim 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. discard """
  2. output: '''
  3. main started: a=10, b=inner-b, c=10, d=some-d, x=16, z=20
  4. exiting: a=12, b=overriden-b, c=100, msg=bye bye, x=16
  5. '''
  6. """
  7. import macros, tables
  8. template scopeHolder =
  9. 0 # scope revision number
  10. type
  11. BindingsSet = Table[string, NimNode]
  12. proc actualBody(n: NimNode): NimNode =
  13. # skip over the double StmtList node introduced in `mergeScopes`
  14. result = n.body
  15. if result.kind == nnkStmtList and result[0].kind == nnkStmtList:
  16. result = result[0]
  17. iterator bindings(n: NimNode, skip = 0): (string, NimNode) =
  18. for i in skip ..< n.len:
  19. let child = n[i]
  20. if child.kind in {nnkAsgn, nnkExprEqExpr}:
  21. let name = $child[0]
  22. let value = child[1]
  23. yield (name, value)
  24. proc scopeRevision(scopeHolder: NimNode): int =
  25. # get the revision number from a scopeHolder sym
  26. assert scopeHolder.kind == nnkSym
  27. var revisionNode = scopeHolder.getImpl.actualBody[0]
  28. result = int(revisionNode.intVal)
  29. proc lastScopeHolder(scopeHolders: NimNode): NimNode =
  30. # get the most recent scopeHolder from a symChoice node
  31. if scopeHolders.kind in {nnkClosedSymChoice, nnkOpenSymChoice}:
  32. var bestScopeRev = 0
  33. assert scopeHolders.len > 0
  34. for scope in scopeHolders:
  35. let rev = scope.scopeRevision
  36. if result == nil or rev > bestScopeRev:
  37. result = scope
  38. bestScopeRev = rev
  39. else:
  40. result = scopeHolders
  41. assert result.kind == nnkSym
  42. macro mergeScopes(scopeHolders: typed, newBindings: untyped): untyped =
  43. var
  44. bestScope = scopeHolders.lastScopeHolder
  45. bestScopeRev = bestScope.scopeRevision
  46. var finalBindings = initTable[string, NimNode]()
  47. for k, v in bindings(bestScope.getImpl.actualBody, skip = 1):
  48. finalBindings[k] = v
  49. for k, v in bindings(newBindings):
  50. finalBindings[k] = v
  51. var newScopeDefinition = newStmtList(newLit(bestScopeRev + 1))
  52. for k, v in finalBindings:
  53. newScopeDefinition.add newAssignment(newIdentNode(k), v)
  54. result = quote:
  55. template scopeHolder = `newScopeDefinition`
  56. template scope(newBindings: untyped) {.dirty.} =
  57. mergeScopes(bindSym"scopeHolder", newBindings)
  58. type
  59. TextLogRecord = object
  60. line: string
  61. StdoutLogRecord = object
  62. template setProperty(r: var TextLogRecord, key: string, val: string, isFirst: bool) =
  63. if not first: r.line.add ", "
  64. r.line.add key
  65. r.line.add "="
  66. r.line.add val
  67. template setEventName(r: var StdoutLogRecord, name: string) =
  68. stdout.write(name & ": ")
  69. template setProperty(r: var StdoutLogRecord, key: string, val: auto, isFirst: bool) =
  70. when not isFirst: stdout.write ", "
  71. stdout.write key
  72. stdout.write "="
  73. stdout.write $val
  74. template flushRecord(r: var StdoutLogRecord) =
  75. stdout.write "\n"
  76. stdout.flushFile
  77. macro logImpl(scopeHolders: typed,
  78. logStmtProps: varargs[untyped]): untyped =
  79. let lexicalScope = scopeHolders.lastScopeHolder.getImpl.actualBody
  80. var finalBindings = initOrderedTable[string, NimNode]()
  81. for k, v in bindings(lexicalScope, skip = 1):
  82. finalBindings[k] = v
  83. for k, v in bindings(logStmtProps, skip = 1):
  84. finalBindings[k] = v
  85. finalBindings.sort(system.cmp)
  86. let eventName = logStmtProps[0]
  87. assert eventName.kind in {nnkStrLit}
  88. let record = genSym(nskVar, "record")
  89. result = quote:
  90. var `record`: StdoutLogRecord
  91. setEventName(`record`, `eventName`)
  92. var isFirst = true
  93. for k, v in finalBindings:
  94. result.add newCall(newIdentNode"setProperty",
  95. record, newLit(k), v, newLit(isFirst))
  96. isFirst = false
  97. result.add newCall(newIdentNode"flushRecord", record)
  98. template log(props: varargs[untyped]) {.dirty.} =
  99. logImpl(bindSym"scopeHolder", props)
  100. scope:
  101. a = 12
  102. b = "original-b"
  103. scope:
  104. x = 16
  105. b = "overriden-b"
  106. scope:
  107. c = 100
  108. proc main =
  109. scope:
  110. c = 10
  111. scope:
  112. z = 20
  113. log("main started", a = 10, b = "inner-b", d = "some-d")
  114. main()
  115. log("exiting", msg = "bye bye")