pylogkit 0.1.0__tar.gz

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.
@@ -0,0 +1,29 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "WebSearch",
5
+ "WebFetch(domain:browniebroke.com)",
6
+ "WebFetch(domain:nhairs.github.io)",
7
+ "WebFetch(domain:www.structlog.org)",
8
+ "WebFetch(domain:betterstack.com)",
9
+ "WebFetch(domain:www.toptal.com)",
10
+ "WebFetch(domain:docs.slack.dev)",
11
+ "WebFetch(domain:www.core27.co)",
12
+ "WebFetch(domain:rednafi.com)",
13
+ "Read(//opt/homebrew/bin/**)",
14
+ "Read(//usr/local/bin/**)",
15
+ "Bash(python3.12 -m venv .venv)",
16
+ "Bash(source .venv/bin/activate)",
17
+ "Bash(pip install:*)",
18
+ "Bash(python -m pytest tests/ -v)",
19
+ "Bash(python -m pytest tests/ --cov=pylogkit --cov-report=term-missing)",
20
+ "Bash(ruff check:*)",
21
+ "Bash(ruff format:*)",
22
+ "Bash(mypy src/pylogkit/)",
23
+ "Bash(python -m pytest tests/ -v --cov=pylogkit --cov-report=term-missing)",
24
+ "Bash(git add:*)",
25
+ "Bash(git commit:*)",
26
+ "Bash(git push:*)"
27
+ ]
28
+ }
29
+ }
@@ -0,0 +1,42 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ lint:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: actions/setup-python@v5
15
+ with:
16
+ python-version: "3.12"
17
+ - run: pip install ruff
18
+ - run: ruff check src/ tests/
19
+ - run: ruff format --check src/ tests/
20
+
21
+ typecheck:
22
+ runs-on: ubuntu-latest
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+ - uses: actions/setup-python@v5
26
+ with:
27
+ python-version: "3.12"
28
+ - run: pip install -e ".[dev]" mypy
29
+ - run: mypy src/pylogkit/
30
+
31
+ test:
32
+ runs-on: ubuntu-latest
33
+ strategy:
34
+ matrix:
35
+ python-version: ["3.11", "3.12", "3.13"]
36
+ steps:
37
+ - uses: actions/checkout@v4
38
+ - uses: actions/setup-python@v5
39
+ with:
40
+ python-version: ${{ matrix.python-version }}
41
+ - run: pip install -e ".[dev]"
42
+ - run: pytest tests/ --cov=pylogkit --cov-report=term-missing --cov-fail-under=80
@@ -0,0 +1,33 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ *.egg-info/
7
+ *.egg
8
+ dist/
9
+ build/
10
+ .eggs/
11
+
12
+ # Virtual environments
13
+ .venv/
14
+ venv/
15
+ env/
16
+
17
+ # Testing
18
+ .pytest_cache/
19
+ .coverage
20
+ htmlcov/
21
+ .mypy_cache/
22
+ .ruff_cache/
23
+
24
+ # IDE
25
+ .idea/
26
+ .vscode/
27
+ *.swp
28
+ *.swo
29
+ *~
30
+
31
+ # OS
32
+ .DS_Store
33
+ Thumbs.db
@@ -0,0 +1,25 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2026-03-31
9
+
10
+ ### Added
11
+
12
+ - `setup_logging()` — one-call logging configuration with auto-format detection
13
+ - `get_logger()` — get logger with optional bound context fields
14
+ - `JsonFormatter` — structured JSON output for production (ISO 8601 UTC timestamps)
15
+ - `ColoredFormatter` — human-readable colored output for development
16
+ - `ContextFilter` — inject static context fields into every log record
17
+ - `ContextVarsFilter` — inject request-scoped context via `contextvars`
18
+ - `RateLimitFilter` — token bucket rate limiting per unique message
19
+ - `DeduplicationFilter` — suppress duplicate messages within a time window
20
+ - `SlackHandler` — async Slack notifications with background thread, queue, and exponential backoff
21
+ - Support for both Slack Bot token and incoming webhook authentication
22
+ - Auto-detection of output format (JSON for non-TTY, colored for TTY)
23
+ - Per-logger level overrides
24
+ - Graceful degradation when optional dependencies are missing
25
+ - Comprehensive test suite (71 tests, 85% coverage)
pylogkit-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Laba
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,341 @@
1
+ Metadata-Version: 2.4
2
+ Name: pylogkit
3
+ Version: 0.1.0
4
+ Summary: Structured logging with Slack integration, rate limiting, and deduplication
5
+ Project-URL: Changelog, https://github.com/analytics-team-global/pylogkit/blob/main/CHANGELOG.md
6
+ Project-URL: Repository, https://github.com/analytics-team-global/pylogkit
7
+ Author: Laba
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: System :: Logging
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.11
20
+ Requires-Dist: python-json-logger<4.0,>=2.0
21
+ Provides-Extra: all
22
+ Requires-Dist: colorlog>=6.0; extra == 'all'
23
+ Requires-Dist: slack-sdk>=3.20; extra == 'all'
24
+ Provides-Extra: color
25
+ Requires-Dist: colorlog>=6.0; extra == 'color'
26
+ Provides-Extra: dev
27
+ Requires-Dist: colorlog>=6.0; extra == 'dev'
28
+ Requires-Dist: mypy>=1.10; extra == 'dev'
29
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
30
+ Requires-Dist: pytest>=8.0; extra == 'dev'
31
+ Requires-Dist: ruff>=0.4; extra == 'dev'
32
+ Requires-Dist: slack-sdk>=3.20; extra == 'dev'
33
+ Provides-Extra: slack
34
+ Requires-Dist: slack-sdk>=3.20; extra == 'slack'
35
+ Description-Content-Type: text/markdown
36
+
37
+ # pylogkit
38
+
39
+ [![CI](https://github.com/analytics-team-global/pylogkit/actions/workflows/ci.yml/badge.svg)](https://github.com/analytics-team-global/pylogkit/actions/workflows/ci.yml)
40
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/downloads/)
41
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
42
+
43
+ Structured logging with Slack integration, rate limiting, and deduplication.
44
+
45
+ Built on top of Python's standard `logging` module — no custom APIs to learn.
46
+
47
+ ## Installation
48
+
49
+ ```bash
50
+ # Core (JSON + colored formatters)
51
+ pip install pylogkit
52
+
53
+ # With Slack support
54
+ pip install pylogkit[slack]
55
+
56
+ # With colored output
57
+ pip install pylogkit[color]
58
+
59
+ # Everything
60
+ pip install pylogkit[all]
61
+ ```
62
+
63
+ Or as a git dependency:
64
+
65
+ ```toml
66
+ # pyproject.toml
67
+ dependencies = [
68
+ "pylogkit[all] @ git+ssh://git@github.com/analytics-team-global/pylogkit.git",
69
+ ]
70
+ ```
71
+
72
+ ## Quick Start
73
+
74
+ ```python
75
+ import os
76
+ from pylogkit import setup_logging, get_logger
77
+
78
+ setup_logging(
79
+ level="INFO",
80
+ service_name="my-api",
81
+ slack_token=os.environ.get("SLACK_BOT_TOKEN"),
82
+ slack_channel="#errors",
83
+ )
84
+
85
+ logger = get_logger(__name__)
86
+
87
+ logger.info("Server started", extra={"port": 8000})
88
+ logger.error("Request failed", extra={"endpoint": "/users", "status": 500})
89
+ ```
90
+
91
+ ## Features
92
+
93
+ ### Auto-detect output format
94
+
95
+ - **TTY (development)**: colored human-readable output with extras
96
+ - **Non-TTY (production/Docker)**: structured JSON
97
+
98
+ Force a specific format:
99
+
100
+ ```python
101
+ setup_logging(json_format=True) # always JSON
102
+ setup_logging(json_format=False) # always colored
103
+ ```
104
+
105
+ ### Structured logging with extra fields
106
+
107
+ Extra fields are included in both JSON and colored output:
108
+
109
+ ```python
110
+ logger.info("Deal processed", extra={
111
+ "deal_id": "123",
112
+ "pipeline": "laba_czech",
113
+ "duration": 1.23,
114
+ })
115
+ ```
116
+
117
+ **JSON output:**
118
+ ```json
119
+ {"timestamp": "2026-03-18T12:00:00+00:00", "level": "INFO", "logger": "myapp.deals", "message": "Deal processed", "deal_id": "123", "pipeline": "laba_czech", "duration": 1.23}
120
+ ```
121
+
122
+ **Colored output:**
123
+ ```
124
+ 2026-03-18 12:00:00 INFO myapp.deals — Deal processed [deal_id=123 pipeline=laba_czech duration=1.23]
125
+ ```
126
+
127
+ ### Logger with static context
128
+
129
+ Bind context fields to a logger — they appear in every message:
130
+
131
+ ```python
132
+ logger = get_logger(__name__, pipeline="laba_czech", env="production")
133
+
134
+ logger.info("Course synced")
135
+ # All messages from this logger will include pipeline= and env=
136
+ ```
137
+
138
+ Calling `get_logger()` again with the same name updates the context:
139
+
140
+ ```python
141
+ logger = get_logger(__name__, pipeline="v2")
142
+ # pipeline is now "v2", env remains from the previous call
143
+ ```
144
+
145
+ ### Request-scoped context with contextvars
146
+
147
+ Inject request-scoped fields (request_id, user_id, etc.) without passing `extra={}` to every log call:
148
+
149
+ ```python
150
+ import contextvars
151
+ from pylogkit import setup_logging
152
+
153
+ log_context: contextvars.ContextVar[dict] = contextvars.ContextVar(
154
+ "log_context", default={}
155
+ )
156
+
157
+ setup_logging(
158
+ service_name="my-api",
159
+ ctx_var=log_context,
160
+ )
161
+
162
+ # In your request handler / middleware:
163
+ log_context.set({"request_id": "abc-123", "user_id": "42"})
164
+ logger.info("Processing request")
165
+ # Output includes request_id and user_id automatically
166
+ ```
167
+
168
+ Works with async frameworks (FastAPI, aiohttp) — each coroutine gets its own context.
169
+
170
+ ### Slack notifications
171
+
172
+ ERROR and CRITICAL logs go to Slack with:
173
+ - Color-coded attachments (yellow/red/dark red)
174
+ - Module and line number
175
+ - Extra fields as "Context" block
176
+ - Formatted traceback
177
+
178
+ ```python
179
+ setup_logging(
180
+ slack_token="xoxb-...",
181
+ slack_channel="#alerts",
182
+ slack_level="ERROR", # minimum level (default: ERROR)
183
+ )
184
+ ```
185
+
186
+ Or use incoming webhook (simpler, scoped to one channel):
187
+
188
+ ```python
189
+ setup_logging(
190
+ slack_webhook_url="https://hooks.slack.com/services/T.../B.../xxx",
191
+ )
192
+ ```
193
+
194
+ **How it works internally:**
195
+ - Messages are queued and sent from a background thread (non-blocking)
196
+ - Respects Slack's 1 msg/sec rate limit
197
+ - Exponential backoff on consecutive failures (up to 5 min)
198
+ - If queue is full (100 messages) — drops silently, never blocks the app
199
+ - If Slack is unreachable — prints to stderr, never crashes
200
+ - Flushes remaining messages on shutdown
201
+
202
+ ### Rate limiting
203
+
204
+ Prevents Slack spam. Each unique message can fire at most N times per period:
205
+
206
+ ```python
207
+ setup_logging(
208
+ slack_token="xoxb-...",
209
+ slack_channel="#alerts",
210
+ slack_rate_limit=1, # max 1 message per period (default)
211
+ slack_rate_period=60.0, # per 60 seconds (default)
212
+ )
213
+ ```
214
+
215
+ If the same error fires 500 times in a minute — only the first one goes to Slack.
216
+
217
+ ### Deduplication
218
+
219
+ Suppresses identical errors within a time window. When the window expires, the next message includes the suppressed count:
220
+
221
+ ```python
222
+ setup_logging(
223
+ slack_token="xoxb-...",
224
+ slack_channel="#alerts",
225
+ slack_dedupe_window=300.0, # 5 minutes (default)
226
+ )
227
+ ```
228
+
229
+ Example: `"Connection refused (suppressed 47 duplicates)"`
230
+
231
+ Set to `0` to disable deduplication.
232
+
233
+ ### Per-logger level overrides
234
+
235
+ Silence noisy third-party libraries:
236
+
237
+ ```python
238
+ setup_logging(
239
+ level="INFO",
240
+ loggers={
241
+ "httpx": "WARNING",
242
+ "sqlalchemy.engine": "WARNING",
243
+ "celery": "INFO",
244
+ },
245
+ )
246
+ ```
247
+
248
+ ### Static context fields
249
+
250
+ Add fields to every log record in the application:
251
+
252
+ ```python
253
+ setup_logging(
254
+ service_name="my-api",
255
+ extra_context={
256
+ "env": "production",
257
+ "region": "eu-west-1",
258
+ },
259
+ )
260
+ ```
261
+
262
+ ## Advanced: using components directly
263
+
264
+ All components can be used independently without `setup_logging()`:
265
+
266
+ ```python
267
+ import logging
268
+ from pylogkit import JsonFormatter, ColoredFormatter, RateLimitFilter, DeduplicationFilter
269
+ from pylogkit import SlackHandler # lazy import, requires slack-sdk
270
+
271
+ # Custom handler with JSON
272
+ handler = logging.StreamHandler()
273
+ handler.setFormatter(JsonFormatter())
274
+
275
+ # Slack with custom filters
276
+ slack = SlackHandler(token="xoxb-...", channel="#alerts")
277
+ slack.addFilter(RateLimitFilter(rate=3, period=120))
278
+ slack.addFilter(DeduplicationFilter(window=600))
279
+
280
+ logging.getLogger().addHandler(handler)
281
+ logging.getLogger().addHandler(slack)
282
+ ```
283
+
284
+ ## Celery integration
285
+
286
+ Prevent Celery from hijacking your logging setup:
287
+
288
+ ```python
289
+ from celery.signals import setup_logging as celery_setup_logging
290
+ from pylogkit import setup_logging
291
+
292
+ @celery_setup_logging.connect
293
+ def configure_celery_logging(**kwargs):
294
+ setup_logging(
295
+ service_name="worker",
296
+ slack_token=os.environ.get("SLACK_BOT_TOKEN"),
297
+ slack_channel="#worker-errors",
298
+ )
299
+ ```
300
+
301
+ ## `setup_logging()` parameters
302
+
303
+ | Parameter | Type | Default | Description |
304
+ |-----------------------|----------------|-----------|--------------------------------------------------------|
305
+ | `level` | `str` | `"INFO"` | Root log level |
306
+ | `service_name` | `str` | `None` | Added to every record as `service` field |
307
+ | `json_format` | `bool` | auto | `True` = JSON, `False` = colored, `None` = auto-detect |
308
+ | `slack_token` | `str` | `None` | Slack Bot token (`xoxb-...`) |
309
+ | `slack_channel` | `str` | `None` | Slack channel (required with token) |
310
+ | `slack_webhook_url` | `str` | `None` | Incoming webhook URL (alternative to token) |
311
+ | `slack_level` | `str` | `"ERROR"` | Minimum level for Slack |
312
+ | `slack_rate_limit` | `int` | `1` | Max messages per period |
313
+ | `slack_rate_period` | `float` | `60.0` | Rate limit window (seconds) |
314
+ | `slack_dedupe_window` | `float` | `300.0` | Deduplication window (seconds), `0` to disable |
315
+ | `extra_context` | `dict` | `None` | Static fields for every record |
316
+ | `loggers` | `dict` | `None` | Per-logger level overrides |
317
+ | `ctx_var` | `ContextVar` | `None` | Request-scoped context variable |
318
+
319
+ ## Development
320
+
321
+ ```bash
322
+ # Install with dev dependencies
323
+ pip install -e ".[dev]"
324
+
325
+ # Run tests
326
+ pytest
327
+
328
+ # Run tests with coverage
329
+ pytest --cov=pylogkit --cov-report=term-missing
330
+
331
+ # Lint
332
+ ruff check src/ tests/
333
+ ruff format src/ tests/
334
+
335
+ # Type check
336
+ mypy src/pylogkit/
337
+ ```
338
+
339
+ ## License
340
+
341
+ [MIT](LICENSE)