koi-net 1.1.0b8__py3-none-any.whl → 1.2.0b2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of koi-net might be problematic. Click here for more details.

Files changed (48) hide show
  1. koi_net/__init__.py +2 -1
  2. koi_net/assembler.py +82 -0
  3. koi_net/cli/__init__.py +1 -0
  4. koi_net/cli/commands.py +99 -0
  5. koi_net/cli/models.py +41 -0
  6. koi_net/config.py +34 -0
  7. koi_net/context.py +11 -28
  8. koi_net/core.py +63 -179
  9. koi_net/default_actions.py +10 -1
  10. koi_net/effector.py +61 -34
  11. koi_net/handshaker.py +39 -0
  12. koi_net/identity.py +2 -3
  13. koi_net/interfaces/entrypoint.py +5 -0
  14. koi_net/interfaces/worker.py +17 -0
  15. koi_net/lifecycle.py +85 -48
  16. koi_net/logger.py +176 -0
  17. koi_net/network/error_handler.py +18 -16
  18. koi_net/network/event_queue.py +17 -185
  19. koi_net/network/graph.py +15 -10
  20. koi_net/network/poll_event_buffer.py +26 -0
  21. koi_net/network/request_handler.py +54 -47
  22. koi_net/network/resolver.py +18 -21
  23. koi_net/network/response_handler.py +79 -15
  24. koi_net/poller.py +18 -9
  25. koi_net/processor/event_worker.py +117 -0
  26. koi_net/processor/handler.py +4 -2
  27. koi_net/processor/{default_handlers.py → handlers.py} +109 -59
  28. koi_net/processor/knowledge_object.py +19 -7
  29. koi_net/processor/kobj_queue.py +51 -0
  30. koi_net/processor/kobj_worker.py +44 -0
  31. koi_net/processor/{knowledge_pipeline.py → pipeline.py} +31 -53
  32. koi_net/protocol/api_models.py +7 -3
  33. koi_net/protocol/envelope.py +5 -6
  34. koi_net/protocol/model_map.py +61 -0
  35. koi_net/protocol/node.py +3 -3
  36. koi_net/protocol/secure.py +8 -8
  37. koi_net/secure.py +33 -13
  38. koi_net/sentry.py +13 -0
  39. koi_net/server.py +44 -78
  40. koi_net/utils.py +18 -0
  41. {koi_net-1.1.0b8.dist-info → koi_net-1.2.0b2.dist-info}/METADATA +8 -3
  42. koi_net-1.2.0b2.dist-info/RECORD +52 -0
  43. koi_net-1.2.0b2.dist-info/entry_points.txt +2 -0
  44. koi_net/actor.py +0 -60
  45. koi_net/processor/interface.py +0 -101
  46. koi_net-1.1.0b8.dist-info/RECORD +0 -38
  47. {koi_net-1.1.0b8.dist-info → koi_net-1.2.0b2.dist-info}/WHEEL +0 -0
  48. {koi_net-1.1.0b8.dist-info → koi_net-1.2.0b2.dist-info}/licenses/LICENSE +0 -0
koi_net/effector.py CHANGED
@@ -1,33 +1,43 @@
1
- import logging
1
+ import structlog
2
2
  from typing import Callable
3
3
  from enum import StrEnum
4
4
  from rid_lib.ext import Cache, Bundle
5
5
  from rid_lib.core import RID, RIDType
6
6
  from rid_lib.types import KoiNetNode
7
+ from .network.resolver import NetworkResolver
8
+ from .processor.kobj_queue import KobjQueue
9
+ from .identity import NodeIdentity
7
10
 
8
- from typing import TYPE_CHECKING
11
+ log = structlog.stdlib.get_logger()
9
12
 
10
- if TYPE_CHECKING:
11
- from .network.resolver import NetworkResolver
12
- from .processor.interface import ProcessorInterface
13
- from .context import ActionContext
14
13
 
15
- logger = logging.getLogger(__name__)
14
+ class ActionContext:
15
+ """Provides action handlers access to other subsystems."""
16
+
17
+ identity: NodeIdentity
16
18
 
19
+ def __init__(
20
+ self,
21
+ identity: NodeIdentity,
22
+ ):
23
+ self.identity = identity
24
+
17
25
 
18
26
  class BundleSource(StrEnum):
19
27
  CACHE = "CACHE"
20
28
  ACTION = "ACTION"
21
29
 
22
30
  class Effector:
31
+ """Subsystem for dereferencing RIDs."""
32
+
23
33
  cache: Cache
24
- resolver: "NetworkResolver | None"
25
- processor: "ProcessorInterface | None"
26
- action_context: "ActionContext | None"
34
+ resolver: NetworkResolver
35
+ kobj_queue: KobjQueue | None
36
+ action_context: ActionContext | None
27
37
  _action_table: dict[
28
38
  type[RID],
29
39
  Callable[
30
- ["ActionContext", RID],
40
+ [ActionContext, RID],
31
41
  Bundle | None
32
42
  ]
33
43
  ] = dict()
@@ -35,22 +45,16 @@ class Effector:
35
45
  def __init__(
36
46
  self,
37
47
  cache: Cache,
48
+ resolver: NetworkResolver,
49
+ kobj_queue: KobjQueue,
50
+ identity: NodeIdentity
38
51
  ):
39
52
  self.cache = cache
40
- self.resolver = None
41
- self.processor = None
42
- self.action_context = None
43
- self._action_table = self.__class__._action_table.copy()
44
-
45
- def set_processor(self, processor: "ProcessorInterface"):
46
- self.processor = processor
47
-
48
- def set_resolver(self, resolver: "NetworkResolver"):
49
53
  self.resolver = resolver
50
-
51
- def set_action_context(self, action_context: "ActionContext"):
52
- self.action_context = action_context
53
-
54
+ self.kobj_queue = kobj_queue
55
+ self.action_context = ActionContext(identity)
56
+ self._action_table = self.__class__._action_table.copy()
57
+
54
58
  @classmethod
55
59
  def register_default_action(cls, rid_type: RIDType):
56
60
  def decorator(func: Callable) -> Callable:
@@ -59,6 +63,16 @@ class Effector:
59
63
  return decorator
60
64
 
61
65
  def register_action(self, rid_type: RIDType):
66
+ """Registers a new dereference action for an RID type.
67
+
68
+ Example:
69
+ This function should be used as a decorator on an action function::
70
+
71
+ @node.register_action(KoiNetNode)
72
+ def deref_koi_net_node(ctx: ActionContext, rid: KoiNetNode):
73
+ # return a Bundle or None
74
+ return
75
+ """
62
76
  def decorator(func: Callable) -> Callable:
63
77
  self._action_table[rid_type] = func
64
78
  return func
@@ -68,18 +82,18 @@ class Effector:
68
82
  bundle = self.cache.read(rid)
69
83
 
70
84
  if bundle:
71
- logger.debug("Cache hit")
85
+ log.debug("Cache hit")
72
86
  return bundle, BundleSource.CACHE
73
87
  else:
74
- logger.debug("Cache miss")
88
+ log.debug("Cache miss")
75
89
  return None
76
90
 
77
91
  def _try_action(self, rid: RID) -> tuple[Bundle, BundleSource] | None:
78
92
  if type(rid) not in self._action_table:
79
- logger.debug("No action available")
93
+ log.debug("No action available")
80
94
  return None
81
95
 
82
- logger.debug("Action available")
96
+ log.debug("Action available")
83
97
  func = self._action_table[type(rid)]
84
98
  bundle = func(
85
99
  ctx=self.action_context,
@@ -87,10 +101,10 @@ class Effector:
87
101
  )
88
102
 
89
103
  if bundle:
90
- logger.debug("Action hit")
104
+ log.debug("Action hit")
91
105
  return bundle, BundleSource.ACTION
92
106
  else:
93
- logger.debug("Action miss")
107
+ log.debug("Action miss")
94
108
  return None
95
109
 
96
110
 
@@ -98,10 +112,10 @@ class Effector:
98
112
  bundle, source = self.resolver.fetch_remote_bundle(rid)
99
113
 
100
114
  if bundle:
101
- logger.debug("Network hit")
115
+ log.debug("Network hit")
102
116
  return bundle, source
103
117
  else:
104
- logger.debug("Network miss")
118
+ log.debug("Network miss")
105
119
  return None
106
120
 
107
121
 
@@ -112,7 +126,20 @@ class Effector:
112
126
  use_network: bool = False,
113
127
  handle_result: bool = True
114
128
  ) -> Bundle | None:
115
- logger.debug(f"Dereferencing {rid!r}")
129
+ """Dereferences an RID.
130
+
131
+ Attempts to dereference an RID by (in order) reading the cache,
132
+ calling a bound action, or fetching from other nodes in the
133
+ newtork.
134
+
135
+ Args:
136
+ rid: RID to dereference
137
+ refresh_cache: skips cache read when `True`
138
+ use_network: enables fetching from other nodes when `True`
139
+ handle_result: handles resulting bundle with knowledge pipeline when `True`
140
+ """
141
+
142
+ log.debug(f"Dereferencing {rid!r}")
116
143
 
117
144
  bundle, source = (
118
145
  # if `refresh_cache`, skip try cache
@@ -128,7 +155,7 @@ class Effector:
128
155
  and bundle is not None
129
156
  and source != BundleSource.CACHE
130
157
  ):
131
- self.processor.handle(
158
+ self.kobj_queue.put_kobj(
132
159
  bundle=bundle,
133
160
  source=source if type(source) is KoiNetNode else None
134
161
  )
koi_net/handshaker.py ADDED
@@ -0,0 +1,39 @@
1
+ import structlog
2
+ from rid_lib.ext import Cache
3
+ from rid_lib.types import KoiNetNode
4
+ from koi_net.identity import NodeIdentity
5
+ from koi_net.network.event_queue import EventQueue
6
+ from .protocol.event import Event, EventType
7
+
8
+ log = structlog.stdlib.get_logger()
9
+
10
+
11
+ class Handshaker:
12
+ def __init__(
13
+ self,
14
+ cache: Cache,
15
+ identity: NodeIdentity,
16
+ event_queue: EventQueue
17
+ ):
18
+ self.cache = cache
19
+ self.identity = identity
20
+ self.event_queue = event_queue
21
+
22
+ def handshake_with(self, target: KoiNetNode):
23
+ """Initiates a handshake with target node.
24
+ Pushes successive `FORGET` and `NEW` events to target node to
25
+ reset the target's cache in case it already knew this node.
26
+ """
27
+ log.debug(f"Initiating handshake with {target}")
28
+ self.event_queue.push_event_to(
29
+ Event.from_rid(
30
+ event_type=EventType.FORGET,
31
+ rid=self.identity.rid),
32
+ target=target
33
+ )
34
+ self.event_queue.push_event_to(
35
+ event=Event.from_bundle(
36
+ event_type=EventType.NEW,
37
+ bundle=self.cache.read(self.identity.rid)),
38
+ target=target
39
+ )
koi_net/identity.py CHANGED
@@ -1,10 +1,9 @@
1
- import logging
1
+ import structlog
2
2
  from rid_lib.types.koi_net_node import KoiNetNode
3
3
  from .config import NodeConfig
4
4
  from .protocol.node import NodeProfile
5
5
 
6
-
7
- logger = logging.getLogger(__name__)
6
+ log = structlog.stdlib.get_logger()
8
7
 
9
8
 
10
9
  class NodeIdentity:
@@ -0,0 +1,5 @@
1
+ from typing import Protocol
2
+
3
+
4
+ class EntryPoint(Protocol):
5
+ def run(self): ...
@@ -0,0 +1,17 @@
1
+ import threading
2
+
3
+
4
+ class End:
5
+ """Class for a sentinel value by knowledge handlers."""
6
+ pass
7
+
8
+ STOP_WORKER = End()
9
+
10
+ class ThreadWorker:
11
+ thread: threading.Thread
12
+
13
+ def __init__(self):
14
+ self.thread = threading.Thread(target=self.run)
15
+
16
+ def run(self):
17
+ ...
koi_net/lifecycle.py CHANGED
@@ -1,104 +1,141 @@
1
- import logging
1
+ import structlog
2
2
  from contextlib import contextmanager, asynccontextmanager
3
3
 
4
+ from rid_lib.ext import Bundle, Cache
4
5
  from rid_lib.types import KoiNetNode
5
6
 
6
- from .actor import Actor
7
- from .effector import Effector
7
+ from .handshaker import Handshaker
8
+ from .network.request_handler import RequestHandler
9
+ from .processor.kobj_worker import KnowledgeProcessingWorker
10
+ from .network.event_queue import EventQueue
11
+ from .processor.event_worker import EventProcessingWorker
12
+ from .protocol.api_models import ErrorResponse
13
+ from .interfaces.worker import STOP_WORKER
8
14
  from .config import NodeConfig
9
- from .processor.interface import ProcessorInterface
15
+ from .processor.kobj_queue import KobjQueue
10
16
  from .network.graph import NetworkGraph
11
17
  from .identity import NodeIdentity
12
18
 
13
- logger = logging.getLogger(__name__)
19
+ log = structlog.stdlib.get_logger()
14
20
 
15
21
 
16
22
  class NodeLifecycle:
23
+ """Manages node startup and shutdown processes."""
24
+
17
25
  config: NodeConfig
26
+ identity: NodeIdentity
18
27
  graph: NetworkGraph
19
- processor: ProcessorInterface
20
- effector: Effector
21
- actor: Actor
28
+ kobj_queue: KobjQueue
29
+ kobj_worker: KnowledgeProcessingWorker
30
+ event_queue: EventQueue
31
+ event_worker: EventProcessingWorker
32
+ cache: Cache
33
+ handshaker: Handshaker
34
+ request_handler: RequestHandler
22
35
 
23
36
  def __init__(
24
37
  self,
25
38
  config: NodeConfig,
26
39
  identity: NodeIdentity,
27
40
  graph: NetworkGraph,
28
- processor: ProcessorInterface,
29
- effector: Effector,
30
- actor: Actor,
31
- use_kobj_processor_thread: bool
41
+ kobj_queue: KobjQueue,
42
+ kobj_worker: KnowledgeProcessingWorker,
43
+ event_queue: EventQueue,
44
+ event_worker: EventProcessingWorker,
45
+ cache: Cache,
46
+ handshaker: Handshaker,
47
+ request_handler: RequestHandler
32
48
  ):
33
49
  self.config = config
34
50
  self.identity = identity
35
51
  self.graph = graph
36
- self.processor = processor
37
- self.effector = effector
38
- self.actor = actor
39
- self.use_kobj_processor_thread = use_kobj_processor_thread
52
+ self.kobj_queue = kobj_queue
53
+ self.kobj_worker = kobj_worker
54
+ self.event_queue = event_queue
55
+ self.event_worker = event_worker
56
+ self.cache = cache
57
+ self.handshaker = handshaker
58
+ self.request_handler = request_handler
40
59
 
41
60
  @contextmanager
42
61
  def run(self):
62
+ """Synchronous context manager for node startup and shutdown."""
43
63
  try:
44
- logger.info("Starting node lifecycle...")
64
+ log.info("Starting node lifecycle...")
45
65
  self.start()
46
66
  yield
47
67
  except KeyboardInterrupt:
48
- logger.info("Keyboard interrupt!")
68
+ log.info("Keyboard interrupt!")
49
69
  finally:
50
- logger.info("Stopping node lifecycle...")
70
+ log.info("Stopping node lifecycle...")
51
71
  self.stop()
52
72
 
53
73
  @asynccontextmanager
54
74
  async def async_run(self):
75
+ """Asynchronous context manager for node startup and shutdown."""
55
76
  try:
56
- logger.info("Starting async node lifecycle...")
77
+ log.info("Starting async node lifecycle...")
57
78
  self.start()
58
79
  yield
59
80
  except KeyboardInterrupt:
60
- logger.info("Keyboard interrupt!")
81
+ log.info("Keyboard interrupt!")
61
82
  finally:
62
- logger.info("Stopping async node lifecycle...")
83
+ log.info("Stopping async node lifecycle...")
63
84
  self.stop()
64
85
 
65
86
  def start(self):
66
- """Starts a node, call this method first.
87
+ """Starts a node.
67
88
 
68
- Starts the processor thread (if enabled). Loads event queues into memory. Generates network graph from nodes and edges in cache. Processes any state changes of node bundle. Initiates handshake with first contact (if provided) if node doesn't have any neighbors.
89
+ Starts the processor thread (if enabled). Generates network
90
+ graph from nodes and edges in cache. Processes any state changes
91
+ of node bundle. Initiates handshake with first contact if node
92
+ doesn't have any neighbors. Catches up with coordinator state.
69
93
  """
70
- if self.use_kobj_processor_thread:
71
- logger.info("Starting processor worker thread")
72
- self.processor.worker_thread.start()
94
+ log.info("Starting processor worker thread")
73
95
 
96
+ self.kobj_worker.thread.start()
97
+ self.event_worker.thread.start()
74
98
  self.graph.generate()
75
99
 
76
100
  # refresh to reflect changes (if any) in config.yaml
77
- self.effector.deref(self.identity.rid, refresh_cache=True)
78
101
 
79
- logger.debug("Waiting for kobj queue to empty")
80
- if self.use_kobj_processor_thread:
81
- self.processor.kobj_queue.join()
82
- else:
83
- self.processor.flush_kobj_queue()
84
- logger.debug("Done")
85
-
86
- if not self.graph.get_neighbors() and self.config.koi_net.first_contact.rid:
87
- logger.debug(f"I don't have any neighbors, reaching out to first contact {self.config.koi_net.first_contact.rid!r}")
88
-
89
- self.actor.handshake_with(self.config.koi_net.first_contact.rid)
102
+ self.kobj_queue.put_kobj(bundle=Bundle.generate(
103
+ rid=self.identity.rid,
104
+ contents=self.identity.profile.model_dump()
105
+ ))
106
+
107
+ log.debug("Waiting for kobj queue to empty")
108
+ self.kobj_queue.q.join()
90
109
 
91
- for coordinator in self.actor.identify_coordinators():
92
- self.actor.catch_up_with(coordinator, rid_types=[KoiNetNode])
110
+ coordinators = self.graph.get_neighbors(direction="in", allowed_type=KoiNetNode)
111
+
112
+ if len(coordinators) > 0:
113
+ for coordinator in coordinators:
114
+ payload = self.request_handler.fetch_manifests(
115
+ node=coordinator,
116
+ rid_types=[KoiNetNode]
117
+ )
118
+ if type(payload) is ErrorResponse:
119
+ continue
120
+
121
+ for manifest in payload.manifests:
122
+ self.kobj_queue.put_kobj(
123
+ manifest=manifest,
124
+ source=coordinator
125
+ )
126
+
127
+ elif self.config.koi_net.first_contact.rid:
128
+ log.debug(f"I don't have any edges with coordinators, reaching out to first contact {self.config.koi_net.first_contact.rid!r}")
129
+
130
+ self.handshaker.handshake_with(self.config.koi_net.first_contact.rid)
93
131
 
94
132
 
95
133
  def stop(self):
96
- """Stops a node, call this method last.
134
+ """Stops a node.
97
135
 
98
- Finishes processing knowledge object queue. Saves event queues to storage.
136
+ Finishes processing knowledge object queue.
99
137
  """
100
- if self.use_kobj_processor_thread:
101
- logger.info(f"Waiting for kobj queue to empty ({self.processor.kobj_queue.unfinished_tasks} tasks remaining)")
102
- self.processor.kobj_queue.join()
103
- else:
104
- self.processor.flush_kobj_queue()
138
+ log.info(f"Waiting for kobj queue to empty ({self.kobj_queue.q.unfinished_tasks} tasks remaining)")
139
+
140
+ self.kobj_queue.q.put(STOP_WORKER)
141
+ self.event_queue.q.put(STOP_WORKER)
koi_net/logger.py ADDED
@@ -0,0 +1,176 @@
1
+ from datetime import datetime
2
+ import logging
3
+ from logging.handlers import RotatingFileHandler
4
+ import colorama
5
+ import structlog
6
+ import sys
7
+ # from sentry_sdk import logger as sentry_logger
8
+
9
+
10
+ def my_processor(_, __, event: dict):
11
+ # print(_, __, event)
12
+ event["path"] = event["module"] + "." + event["func_name"]
13
+ return event
14
+
15
+ # def sentry_processor(_, method, event: dict):
16
+ # print(event)
17
+ # if method == "critical":
18
+ # sentry_logger.fatal(
19
+ # event["event"],
20
+ # attributes=event
21
+ # )
22
+ # elif method == "info":
23
+ # sentry_logger.info(
24
+ # event["event"],
25
+ # attributes=event
26
+ # )
27
+ # elif method == "debug":
28
+ # sentry_logger.debug(
29
+ # event["event"],
30
+ # attributes=event
31
+ # )
32
+ # return event
33
+
34
+ console_renderer = structlog.dev.ConsoleRenderer(
35
+ columns=[
36
+ # Render the timestamp without the key name in yellow.
37
+ structlog.dev.Column(
38
+ "timestamp",
39
+ structlog.dev.KeyValueColumnFormatter(
40
+ key_style=None,
41
+ value_style=colorama.Style.DIM,
42
+ reset_style=colorama.Style.RESET_ALL,
43
+ value_repr=lambda t: datetime.fromisoformat(t).strftime("%Y-%m-%d %H:%M:%S"),
44
+ ),
45
+ ),
46
+ structlog.dev.Column(
47
+ "level",
48
+ structlog.dev.LogLevelColumnFormatter(
49
+ level_styles={
50
+ level: colorama.Style.BRIGHT + color
51
+ for level, color in {
52
+ "critical": colorama.Fore.RED,
53
+ "exception": colorama.Fore.RED,
54
+ "error": colorama.Fore.RED,
55
+ "warn": colorama.Fore.YELLOW,
56
+ "warning": colorama.Fore.YELLOW,
57
+ "info": colorama.Fore.GREEN,
58
+ "debug": colorama.Fore.GREEN,
59
+ "notset": colorama.Back.RED,
60
+ }.items()
61
+ },
62
+ reset_style=colorama.Style.RESET_ALL,
63
+ width=9
64
+ )
65
+ ),
66
+ # Render the event without the key name in bright magenta.
67
+
68
+ # Default formatter for all keys not explicitly mentioned. The key is
69
+ # cyan, the value is green.
70
+ structlog.dev.Column(
71
+ "path",
72
+ structlog.dev.KeyValueColumnFormatter(
73
+ key_style=None,
74
+ value_style=colorama.Fore.MAGENTA,
75
+ reset_style=colorama.Style.RESET_ALL,
76
+ value_repr=str,
77
+ width=30
78
+ ),
79
+ ),
80
+ # structlog.dev.Column(
81
+ # "func_name",
82
+ # structlog.dev.KeyValueColumnFormatter(
83
+ # key_style=None,
84
+ # value_style=colorama.Fore.MAGENTA,
85
+ # reset_style=colorama.Style.RESET_ALL,
86
+ # value_repr=str,
87
+ # prefix="(",
88
+ # postfix=")",
89
+ # width=15
90
+ # ),
91
+ # ),
92
+ structlog.dev.Column(
93
+ "event",
94
+ structlog.dev.KeyValueColumnFormatter(
95
+ key_style=None,
96
+ value_style=colorama.Fore.WHITE,
97
+ reset_style=colorama.Style.RESET_ALL,
98
+ value_repr=str,
99
+ width=30
100
+ ),
101
+ ),
102
+ structlog.dev.Column(
103
+ "",
104
+ structlog.dev.KeyValueColumnFormatter(
105
+ key_style=colorama.Fore.BLUE,
106
+ value_style=colorama.Fore.GREEN,
107
+ reset_style=colorama.Style.RESET_ALL,
108
+ value_repr=str,
109
+ ),
110
+ )
111
+ ]
112
+ )
113
+
114
+ structlog.configure(
115
+ processors=[
116
+ # If log level is too low, abort pipeline and throw away log entry.
117
+ structlog.stdlib.filter_by_level,
118
+ # Add the name of the logger to event dict.
119
+ structlog.stdlib.add_logger_name,
120
+ # Add log level to event dict.
121
+ structlog.stdlib.add_log_level,
122
+ # Perform %-style formatting.
123
+ structlog.stdlib.PositionalArgumentsFormatter(),
124
+ # Add a timestamp in ISO 8601 format.
125
+ structlog.processors.TimeStamper(fmt="iso"),
126
+ # If the "stack_info" key in the event dict is true, remove it and
127
+ # render the current stack trace in the "stack" key.
128
+ structlog.processors.StackInfoRenderer(),
129
+ # If the "exc_info" key in the event dict is either true or a
130
+ # sys.exc_info() tuple, remove "exc_info" and render the exception
131
+ # with traceback into the "exception" key.
132
+ # structlog.processors.format_exc_info,
133
+ # If some value is in bytes, decode it to a Unicode str.
134
+ structlog.processors.UnicodeDecoder(),
135
+ # Add callsite parameters.
136
+ structlog.processors.CallsiteParameterAdder(
137
+ {
138
+ structlog.processors.CallsiteParameter.MODULE,
139
+ structlog.processors.CallsiteParameter.FUNC_NAME,
140
+ # structlog.processors.CallsiteParameter.LINENO,
141
+ }
142
+ ),
143
+ my_processor,
144
+ # Render the final event dict as JSON.
145
+ # sentry_processor,
146
+ console_renderer
147
+ # structlog.processors.JSONRenderer()
148
+
149
+ ],
150
+ # `wrapper_class` is the bound logger that you get back from
151
+ # get_logger(). This one imitates the API of `logging.Logger`.
152
+ wrapper_class=structlog.stdlib.BoundLogger,
153
+ # `logger_factory` is used to create wrapped loggers that are used for
154
+ # OUTPUT. This one returns a `logging.Logger`. The final value (a JSON
155
+ # string) from the final processor (`JSONRenderer`) will be passed to
156
+ # the method of the same name as that you've called on the bound logger.
157
+ logger_factory=structlog.stdlib.LoggerFactory(),
158
+ # Effectively freeze configuration after creating the first bound
159
+ # logger.
160
+ cache_logger_on_first_use=True,
161
+ )
162
+
163
+ file_handler = RotatingFileHandler(
164
+ filename="app.log",
165
+ maxBytes=10 * 1024 * 1024,
166
+ backupCount=5,
167
+ encoding="utf-8"
168
+ )
169
+
170
+ logging.basicConfig(
171
+ format="%(message)s",
172
+ stream=sys.stdout,
173
+ level=logging.INFO,
174
+ )
175
+
176
+ # log = structlog.stdlib.get_logger()