Skip to content

Commit 6e63509

Browse files
committed
feat: add PackIdGenerator for SLS integration
1 parent 632a879 commit 6e63509

4 files changed

Lines changed: 85 additions & 3 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "ulogger"
7-
version = "0.1.6"
7+
version = "0.1.7"
88
description = "Universal Python logging utilities with SLS and session support"
99
requires-python = ">=3.9"
1010
authors = [{name="0xJacky", email="me@jackyu.cn"}]

src/ulogger/sls.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,44 @@
11
"""SLS (Simple Log Service) integration for Alibaba Cloud."""
22

3+
import hashlib
34
import json
45
import logging
6+
import os
7+
import secrets
8+
import socket
59
import threading
610
import time
711
from dataclasses import dataclass
812
from typing import Dict, List, Optional, Tuple
913

1014

15+
class PackIdGenerator:
16+
"""生成符合阿里云 PackId 规范的标识符,线程安全"""
17+
18+
_PREFIX_LENGTH = 16
19+
20+
def __init__(self, prefix: Optional[str] = None) -> None:
21+
self._lock = threading.Lock()
22+
self._counter = 0
23+
self._prefix = (prefix or self._build_prefix()).upper()
24+
25+
def generate(self) -> str:
26+
"""生成新的 PackId,格式为 <前缀>-<递增十六进制序号>"""
27+
with self._lock:
28+
self._counter += 1
29+
sequence = self._counter
30+
return f"{self._prefix}-{sequence:X}"
31+
32+
def _build_prefix(self) -> str:
33+
hostname = socket.gethostname()
34+
pid = os.getpid()
35+
now_ns = time.time_ns()
36+
entropy = secrets.token_hex(8)
37+
payload = f"{hostname}|{pid}|{now_ns}|{entropy}".encode("utf-8")
38+
digest = hashlib.sha256(payload).hexdigest().upper()
39+
return digest[: self._PREFIX_LENGTH]
40+
41+
1142
@dataclass
1243
class SLSConfig:
1344
"""SLS configuration data class"""
@@ -217,6 +248,7 @@ def __init__(
217248
self._PutLogsRequest = put_logs_request_cls
218249
self._LogException = log_exception_cls
219250
self._lock = threading.Lock()
251+
self._pack_id_generator = PackIdGenerator()
220252

221253
def emit(self, record: logging.LogRecord) -> None:
222254
if not self._client:
@@ -239,6 +271,9 @@ def emit(self, record: logging.LogRecord) -> None:
239271
logitems=[log_item],
240272
)
241273

274+
pack_id = self._pack_id_generator.generate()
275+
self._attach_pack_id(request, pack_id)
276+
242277
with self._lock:
243278
self._client.put_logs(request)
244279

@@ -341,6 +376,25 @@ def _resolve_timestamp(record: logging.LogRecord) -> Tuple[int, int]:
341376
now_ns = time.time_ns()
342377
return now_ns // 1_000_000_000, now_ns % 1_000_000_000
343378

379+
def _attach_pack_id(self, request, pack_id: str) -> None:
380+
tag_payload = [("__pack_id__", pack_id)]
381+
382+
set_logtags = getattr(request, "set_logtags", None)
383+
if callable(set_logtags):
384+
set_logtags(tag_payload)
385+
return
386+
387+
set_log_tags = getattr(request, "set_log_tags", None)
388+
if callable(set_log_tags):
389+
set_log_tags(tag_payload)
390+
return
391+
392+
if hasattr(request, "logtags"):
393+
request.logtags = tag_payload
394+
return
395+
396+
request.log_tags = tag_payload
397+
344398
@classmethod
345399
def create(cls, config: SLSConfig) -> Optional["SLSPropagateHandler"]:
346400
"""Create SLS handler if configuration is valid"""

tests/test_sls.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,27 @@
77

88
import pytest
99

10-
from ulogger.sls import SLSClient, SLSConfig, SLSPropagateHandler
10+
from ulogger.sls import PackIdGenerator, SLSClient, SLSConfig, SLSPropagateHandler
11+
12+
13+
class TestPackIdGenerator:
14+
"""Test PackIdGenerator behavior"""
15+
16+
def test_generate_sequence_and_format(self):
17+
generator = PackIdGenerator()
18+
19+
first = generator.generate()
20+
second = generator.generate()
21+
22+
assert first != second
23+
24+
prefix1, seq1 = first.split("-")
25+
prefix2, seq2 = second.split("-")
26+
27+
assert len(prefix1) == 16
28+
assert prefix1 == prefix1.upper()
29+
assert prefix1 == prefix2
30+
assert int(seq2, 16) == int(seq1, 16) + 1
1131

1232

1333
class TestSLSConfig:
@@ -364,6 +384,7 @@ def test_emit(self):
364384
client = MagicMock()
365385
log_item = MagicMock()
366386
put_logs_request = MagicMock()
387+
put_logs_request.set_logtags = MagicMock()
367388

368389
handler = SLSPropagateHandler(
369390
client,
@@ -389,6 +410,13 @@ def test_emit(self):
389410
assert log_item.set_time.call_count == 1
390411
assert log_item.set_time_nano_part.call_count == 1
391412
log_item.set_contents.assert_called_once()
413+
put_logs_request.set_logtags.assert_called_once()
414+
tags = put_logs_request.set_logtags.call_args[0][0]
415+
assert tags[0][0] == "__pack_id__"
416+
pack_id_value = tags[0][1]
417+
prefix, seq = pack_id_value.split("-")
418+
assert len(prefix) == 16
419+
assert int(seq, 16) >= 1
392420
client.put_logs.assert_called_once_with(put_logs_request)
393421

394422
def test_emit_logger_disabled(self):

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)