webscout 8.3.6__py3-none-any.whl → 2025.10.11__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 webscout might be problematic. Click here for more details.
- webscout/AIauto.py +250 -250
- webscout/AIbase.py +379 -379
- webscout/AIutel.py +60 -58
- webscout/Bard.py +1012 -1012
- webscout/Bing_search.py +417 -417
- webscout/DWEBS.py +529 -529
- webscout/Extra/Act.md +309 -309
- webscout/Extra/GitToolkit/__init__.py +10 -10
- webscout/Extra/GitToolkit/gitapi/README.md +110 -110
- webscout/Extra/GitToolkit/gitapi/__init__.py +11 -11
- webscout/Extra/GitToolkit/gitapi/repository.py +195 -195
- webscout/Extra/GitToolkit/gitapi/user.py +96 -96
- webscout/Extra/GitToolkit/gitapi/utils.py +61 -61
- webscout/Extra/YTToolkit/README.md +375 -375
- webscout/Extra/YTToolkit/YTdownloader.py +956 -956
- webscout/Extra/YTToolkit/__init__.py +2 -2
- webscout/Extra/YTToolkit/transcriber.py +475 -475
- webscout/Extra/YTToolkit/ytapi/README.md +44 -44
- webscout/Extra/YTToolkit/ytapi/__init__.py +6 -6
- webscout/Extra/YTToolkit/ytapi/channel.py +307 -307
- webscout/Extra/YTToolkit/ytapi/errors.py +13 -13
- webscout/Extra/YTToolkit/ytapi/extras.py +118 -118
- webscout/Extra/YTToolkit/ytapi/https.py +88 -88
- webscout/Extra/YTToolkit/ytapi/patterns.py +61 -61
- webscout/Extra/YTToolkit/ytapi/playlist.py +58 -58
- webscout/Extra/YTToolkit/ytapi/pool.py +7 -7
- webscout/Extra/YTToolkit/ytapi/query.py +39 -39
- webscout/Extra/YTToolkit/ytapi/stream.py +62 -62
- webscout/Extra/YTToolkit/ytapi/utils.py +62 -62
- webscout/Extra/YTToolkit/ytapi/video.py +232 -232
- webscout/Extra/autocoder/__init__.py +9 -9
- webscout/Extra/autocoder/autocoder.py +1105 -1105
- webscout/Extra/autocoder/autocoder_utiles.py +332 -332
- webscout/Extra/gguf.md +429 -429
- webscout/Extra/gguf.py +1213 -1213
- webscout/Extra/tempmail/README.md +487 -487
- webscout/Extra/tempmail/__init__.py +27 -27
- webscout/Extra/tempmail/async_utils.py +140 -140
- webscout/Extra/tempmail/base.py +160 -160
- webscout/Extra/tempmail/cli.py +186 -186
- webscout/Extra/tempmail/emailnator.py +84 -84
- webscout/Extra/tempmail/mail_tm.py +360 -360
- webscout/Extra/tempmail/temp_mail_io.py +291 -291
- webscout/Extra/weather.md +281 -281
- webscout/Extra/weather.py +193 -193
- webscout/Litlogger/README.md +10 -10
- webscout/Litlogger/__init__.py +15 -15
- webscout/Litlogger/formats.py +13 -13
- webscout/Litlogger/handlers.py +121 -121
- webscout/Litlogger/levels.py +13 -13
- webscout/Litlogger/logger.py +134 -134
- webscout/Provider/AISEARCH/Perplexity.py +332 -332
- webscout/Provider/AISEARCH/README.md +279 -279
- webscout/Provider/AISEARCH/__init__.py +33 -11
- webscout/Provider/AISEARCH/felo_search.py +206 -206
- webscout/Provider/AISEARCH/genspark_search.py +323 -323
- webscout/Provider/AISEARCH/hika_search.py +185 -185
- webscout/Provider/AISEARCH/iask_search.py +410 -410
- webscout/Provider/AISEARCH/monica_search.py +219 -219
- webscout/Provider/AISEARCH/scira_search.py +316 -314
- webscout/Provider/AISEARCH/stellar_search.py +177 -177
- webscout/Provider/AISEARCH/webpilotai_search.py +255 -255
- webscout/Provider/Aitopia.py +314 -315
- webscout/Provider/Andi.py +3 -3
- webscout/Provider/Apriel.py +306 -0
- webscout/Provider/ChatGPTClone.py +236 -236
- webscout/Provider/ChatSandbox.py +343 -342
- webscout/Provider/Cloudflare.py +324 -324
- webscout/Provider/Cohere.py +208 -207
- webscout/Provider/Deepinfra.py +370 -369
- webscout/Provider/ExaAI.py +260 -260
- webscout/Provider/ExaChat.py +308 -387
- webscout/Provider/Flowith.py +221 -221
- webscout/Provider/GMI.py +293 -0
- webscout/Provider/Gemini.py +164 -162
- webscout/Provider/GeminiProxy.py +167 -166
- webscout/Provider/GithubChat.py +371 -370
- webscout/Provider/Groq.py +800 -800
- webscout/Provider/HeckAI.py +383 -379
- webscout/Provider/Jadve.py +282 -297
- webscout/Provider/K2Think.py +308 -0
- webscout/Provider/Koboldai.py +206 -384
- webscout/Provider/LambdaChat.py +423 -425
- webscout/Provider/Nemotron.py +244 -245
- webscout/Provider/Netwrck.py +248 -247
- webscout/Provider/OLLAMA.py +395 -394
- webscout/Provider/OPENAI/Cloudflare.py +394 -395
- webscout/Provider/OPENAI/FalconH1.py +452 -457
- webscout/Provider/OPENAI/FreeGemini.py +297 -299
- webscout/Provider/OPENAI/{monochat.py → K2Think.py} +432 -329
- webscout/Provider/OPENAI/NEMOTRON.py +241 -244
- webscout/Provider/OPENAI/PI.py +428 -427
- webscout/Provider/OPENAI/README.md +959 -959
- webscout/Provider/OPENAI/TogetherAI.py +345 -345
- webscout/Provider/OPENAI/TwoAI.py +466 -467
- webscout/Provider/OPENAI/__init__.py +33 -59
- webscout/Provider/OPENAI/ai4chat.py +313 -303
- webscout/Provider/OPENAI/base.py +249 -269
- webscout/Provider/OPENAI/chatglm.py +528 -0
- webscout/Provider/OPENAI/chatgpt.py +593 -588
- webscout/Provider/OPENAI/chatgptclone.py +521 -524
- webscout/Provider/OPENAI/chatsandbox.py +202 -177
- webscout/Provider/OPENAI/deepinfra.py +319 -315
- webscout/Provider/OPENAI/e2b.py +1665 -1665
- webscout/Provider/OPENAI/exaai.py +420 -420
- webscout/Provider/OPENAI/exachat.py +452 -452
- webscout/Provider/OPENAI/friendli.py +232 -232
- webscout/Provider/OPENAI/{refact.py → gmi.py} +324 -274
- webscout/Provider/OPENAI/groq.py +364 -364
- webscout/Provider/OPENAI/heckai.py +314 -311
- webscout/Provider/OPENAI/llmchatco.py +337 -337
- webscout/Provider/OPENAI/netwrck.py +355 -354
- webscout/Provider/OPENAI/oivscode.py +290 -290
- webscout/Provider/OPENAI/opkfc.py +518 -518
- webscout/Provider/OPENAI/pydantic_imports.py +1 -1
- webscout/Provider/OPENAI/scirachat.py +535 -529
- webscout/Provider/OPENAI/sonus.py +308 -308
- webscout/Provider/OPENAI/standardinput.py +442 -442
- webscout/Provider/OPENAI/textpollinations.py +340 -348
- webscout/Provider/OPENAI/toolbaz.py +419 -413
- webscout/Provider/OPENAI/typefully.py +362 -362
- webscout/Provider/OPENAI/utils.py +295 -295
- webscout/Provider/OPENAI/venice.py +436 -436
- webscout/Provider/OPENAI/wisecat.py +387 -387
- webscout/Provider/OPENAI/writecream.py +166 -166
- webscout/Provider/OPENAI/x0gpt.py +378 -378
- webscout/Provider/OPENAI/yep.py +389 -389
- webscout/Provider/OpenGPT.py +230 -230
- webscout/Provider/Openai.py +244 -496
- webscout/Provider/PI.py +405 -404
- webscout/Provider/Perplexitylabs.py +430 -431
- webscout/Provider/QwenLM.py +272 -254
- webscout/Provider/STT/__init__.py +32 -2
- webscout/Provider/{Llama3.py → Sambanova.py} +257 -258
- webscout/Provider/StandardInput.py +309 -309
- webscout/Provider/TTI/README.md +82 -82
- webscout/Provider/TTI/__init__.py +33 -12
- webscout/Provider/TTI/aiarta.py +413 -413
- webscout/Provider/TTI/base.py +136 -136
- webscout/Provider/TTI/bing.py +243 -243
- webscout/Provider/TTI/gpt1image.py +149 -149
- webscout/Provider/TTI/imagen.py +196 -196
- webscout/Provider/TTI/infip.py +211 -211
- webscout/Provider/TTI/magicstudio.py +232 -232
- webscout/Provider/TTI/monochat.py +219 -219
- webscout/Provider/TTI/piclumen.py +214 -214
- webscout/Provider/TTI/pixelmuse.py +232 -232
- webscout/Provider/TTI/pollinations.py +232 -232
- webscout/Provider/TTI/together.py +288 -288
- webscout/Provider/TTI/utils.py +12 -12
- webscout/Provider/TTI/venice.py +367 -367
- webscout/Provider/TTS/README.md +192 -192
- webscout/Provider/TTS/__init__.py +33 -10
- webscout/Provider/TTS/parler.py +110 -110
- webscout/Provider/TTS/streamElements.py +333 -333
- webscout/Provider/TTS/utils.py +280 -280
- webscout/Provider/TeachAnything.py +237 -236
- webscout/Provider/TextPollinationsAI.py +311 -318
- webscout/Provider/TogetherAI.py +356 -357
- webscout/Provider/TwoAI.py +313 -569
- webscout/Provider/TypliAI.py +312 -311
- webscout/Provider/UNFINISHED/ChatHub.py +208 -208
- webscout/Provider/UNFINISHED/ChutesAI.py +313 -313
- webscout/Provider/{GizAI.py → UNFINISHED/GizAI.py} +294 -294
- webscout/Provider/{Marcus.py → UNFINISHED/Marcus.py} +198 -198
- webscout/Provider/{Qodo.py → UNFINISHED/Qodo.py} +477 -477
- webscout/Provider/UNFINISHED/VercelAIGateway.py +338 -338
- webscout/Provider/{XenAI.py → UNFINISHED/XenAI.py} +324 -324
- webscout/Provider/UNFINISHED/Youchat.py +330 -330
- webscout/Provider/UNFINISHED/liner.py +334 -0
- webscout/Provider/UNFINISHED/liner_api_request.py +262 -262
- webscout/Provider/UNFINISHED/puterjs.py +634 -634
- webscout/Provider/UNFINISHED/samurai.py +223 -223
- webscout/Provider/UNFINISHED/test_lmarena.py +119 -119
- webscout/Provider/Venice.py +251 -250
- webscout/Provider/VercelAI.py +256 -255
- webscout/Provider/WiseCat.py +232 -231
- webscout/Provider/WrDoChat.py +367 -366
- webscout/Provider/__init__.py +33 -86
- webscout/Provider/ai4chat.py +174 -174
- webscout/Provider/akashgpt.py +331 -334
- webscout/Provider/cerebras.py +446 -340
- webscout/Provider/chatglm.py +394 -214
- webscout/Provider/cleeai.py +211 -212
- webscout/Provider/deepseek_assistant.py +1 -1
- webscout/Provider/elmo.py +282 -282
- webscout/Provider/geminiapi.py +208 -208
- webscout/Provider/granite.py +261 -261
- webscout/Provider/hermes.py +263 -265
- webscout/Provider/julius.py +223 -222
- webscout/Provider/learnfastai.py +309 -309
- webscout/Provider/llama3mitril.py +214 -214
- webscout/Provider/llmchat.py +243 -243
- webscout/Provider/llmchatco.py +290 -290
- webscout/Provider/meta.py +801 -801
- webscout/Provider/oivscode.py +309 -309
- webscout/Provider/scira_chat.py +384 -457
- webscout/Provider/searchchat.py +292 -291
- webscout/Provider/sonus.py +258 -258
- webscout/Provider/toolbaz.py +370 -364
- webscout/Provider/turboseek.py +274 -265
- webscout/Provider/typefully.py +208 -207
- webscout/Provider/x0gpt.py +1 -0
- webscout/Provider/yep.py +372 -371
- webscout/__init__.py +30 -31
- webscout/__main__.py +5 -5
- webscout/auth/api_key_manager.py +189 -189
- webscout/auth/config.py +175 -175
- webscout/auth/models.py +185 -185
- webscout/auth/routes.py +664 -664
- webscout/auth/simple_logger.py +236 -236
- webscout/cli.py +523 -523
- webscout/conversation.py +438 -438
- webscout/exceptions.py +361 -361
- webscout/litagent/Readme.md +298 -298
- webscout/litagent/__init__.py +28 -28
- webscout/litagent/agent.py +581 -581
- webscout/litagent/constants.py +59 -59
- webscout/litprinter/__init__.py +58 -58
- webscout/models.py +181 -181
- webscout/optimizers.py +419 -419
- webscout/prompt_manager.py +288 -288
- webscout/sanitize.py +1078 -1078
- webscout/scout/README.md +401 -401
- webscout/scout/__init__.py +8 -8
- webscout/scout/core/__init__.py +6 -6
- webscout/scout/core/crawler.py +297 -297
- webscout/scout/core/scout.py +706 -706
- webscout/scout/core/search_result.py +95 -95
- webscout/scout/core/text_analyzer.py +62 -62
- webscout/scout/core/text_utils.py +277 -277
- webscout/scout/core/web_analyzer.py +51 -51
- webscout/scout/element.py +599 -599
- webscout/scout/parsers/__init__.py +69 -69
- webscout/scout/parsers/html5lib_parser.py +172 -172
- webscout/scout/parsers/html_parser.py +236 -236
- webscout/scout/parsers/lxml_parser.py +178 -178
- webscout/scout/utils.py +37 -37
- webscout/swiftcli/Readme.md +323 -323
- webscout/swiftcli/__init__.py +95 -95
- webscout/swiftcli/core/__init__.py +7 -7
- webscout/swiftcli/core/cli.py +308 -308
- webscout/swiftcli/core/context.py +104 -104
- webscout/swiftcli/core/group.py +241 -241
- webscout/swiftcli/decorators/__init__.py +28 -28
- webscout/swiftcli/decorators/command.py +221 -221
- webscout/swiftcli/decorators/options.py +220 -220
- webscout/swiftcli/decorators/output.py +302 -302
- webscout/swiftcli/exceptions.py +21 -21
- webscout/swiftcli/plugins/__init__.py +9 -9
- webscout/swiftcli/plugins/base.py +135 -135
- webscout/swiftcli/plugins/manager.py +269 -269
- webscout/swiftcli/utils/__init__.py +59 -59
- webscout/swiftcli/utils/formatting.py +252 -252
- webscout/swiftcli/utils/parsing.py +267 -267
- webscout/update_checker.py +117 -117
- webscout/version.py +1 -1
- webscout/webscout_search.py +1183 -1183
- webscout/webscout_search_async.py +649 -649
- webscout/yep_search.py +346 -346
- webscout/zeroart/README.md +89 -89
- webscout/zeroart/__init__.py +134 -134
- webscout/zeroart/base.py +66 -66
- webscout/zeroart/effects.py +100 -100
- webscout/zeroart/fonts.py +1238 -1238
- {webscout-8.3.6.dist-info → webscout-2025.10.11.dist-info}/METADATA +937 -936
- webscout-2025.10.11.dist-info/RECORD +300 -0
- webscout/Provider/AISEARCH/DeepFind.py +0 -254
- webscout/Provider/AllenAI.py +0 -440
- webscout/Provider/Blackboxai.py +0 -793
- webscout/Provider/FreeGemini.py +0 -250
- webscout/Provider/GptOss.py +0 -207
- webscout/Provider/Hunyuan.py +0 -283
- webscout/Provider/Kimi.py +0 -445
- webscout/Provider/MCPCore.py +0 -322
- webscout/Provider/MiniMax.py +0 -207
- webscout/Provider/OPENAI/BLACKBOXAI.py +0 -1045
- webscout/Provider/OPENAI/MiniMax.py +0 -298
- webscout/Provider/OPENAI/Qwen3.py +0 -304
- webscout/Provider/OPENAI/autoproxy.py +0 -1067
- webscout/Provider/OPENAI/copilot.py +0 -321
- webscout/Provider/OPENAI/gptoss.py +0 -288
- webscout/Provider/OPENAI/kimi.py +0 -469
- webscout/Provider/OPENAI/mcpcore.py +0 -431
- webscout/Provider/OPENAI/multichat.py +0 -378
- webscout/Provider/OPENAI/qodo.py +0 -630
- webscout/Provider/OPENAI/xenai.py +0 -514
- webscout/Provider/Reka.py +0 -214
- webscout/Provider/UNFINISHED/fetch_together_models.py +0 -90
- webscout/Provider/asksteve.py +0 -220
- webscout/Provider/copilot.py +0 -441
- webscout/Provider/freeaichat.py +0 -294
- webscout/Provider/koala.py +0 -182
- webscout/Provider/lmarena.py +0 -198
- webscout/Provider/monochat.py +0 -275
- webscout/Provider/multichat.py +0 -375
- webscout/Provider/scnet.py +0 -244
- webscout/Provider/talkai.py +0 -194
- webscout/tempid.py +0 -128
- webscout-8.3.6.dist-info/RECORD +0 -327
- {webscout-8.3.6.dist-info → webscout-2025.10.11.dist-info}/WHEEL +0 -0
- {webscout-8.3.6.dist-info → webscout-2025.10.11.dist-info}/entry_points.txt +0 -0
- {webscout-8.3.6.dist-info → webscout-2025.10.11.dist-info}/licenses/LICENSE.md +0 -0
- {webscout-8.3.6.dist-info → webscout-2025.10.11.dist-info}/top_level.txt +0 -0
webscout/Litlogger/handlers.py
CHANGED
|
@@ -1,121 +1,121 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import socket
|
|
3
|
-
import http.client
|
|
4
|
-
from datetime import datetime
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
from typing import Optional
|
|
7
|
-
|
|
8
|
-
from .levels import LogLevel
|
|
9
|
-
|
|
10
|
-
RESET = "\033[0m"
|
|
11
|
-
LEVEL_COLORS = {
|
|
12
|
-
LogLevel.TRACE: "\033[90m",
|
|
13
|
-
LogLevel.DEBUG: "\033[36m",
|
|
14
|
-
LogLevel.INFO: "\033[32m",
|
|
15
|
-
LogLevel.WARNING: "\033[33m",
|
|
16
|
-
LogLevel.ERROR: "\033[31m",
|
|
17
|
-
LogLevel.CRITICAL: "\033[41m\033[97m",
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
class Handler:
|
|
21
|
-
def __init__(self, level: LogLevel = LogLevel.DEBUG):
|
|
22
|
-
self.level = level
|
|
23
|
-
|
|
24
|
-
def emit(self, message: str, level: LogLevel):
|
|
25
|
-
raise NotImplementedError
|
|
26
|
-
|
|
27
|
-
class ConsoleHandler(Handler):
|
|
28
|
-
def __init__(self, stream=None, level: LogLevel = LogLevel.DEBUG):
|
|
29
|
-
super().__init__(level)
|
|
30
|
-
self.stream = stream or os.sys.stdout
|
|
31
|
-
|
|
32
|
-
def emit(self, message: str, level: LogLevel):
|
|
33
|
-
color = LEVEL_COLORS.get(level, "")
|
|
34
|
-
self.stream.write(f"{color}{message}{RESET}\n")
|
|
35
|
-
self.stream.flush()
|
|
36
|
-
|
|
37
|
-
class FileHandler(Handler):
|
|
38
|
-
def __init__(self, path: str, level: LogLevel = LogLevel.DEBUG, max_bytes: int = 0, backups: int = 0):
|
|
39
|
-
super().__init__(level)
|
|
40
|
-
self.path = Path(path)
|
|
41
|
-
self.max_bytes = max_bytes
|
|
42
|
-
self.backups = backups
|
|
43
|
-
self._open()
|
|
44
|
-
|
|
45
|
-
def _open(self):
|
|
46
|
-
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
47
|
-
self._file = open(self.path, "a", encoding="utf-8")
|
|
48
|
-
|
|
49
|
-
def _rotate(self):
|
|
50
|
-
if self.backups <= 0:
|
|
51
|
-
self._file.close()
|
|
52
|
-
self.path.unlink(missing_ok=True)
|
|
53
|
-
self._open()
|
|
54
|
-
return
|
|
55
|
-
self._file.close()
|
|
56
|
-
for i in range(self.backups, 0, -1):
|
|
57
|
-
src = self.path.with_suffix(f".{i}") if i == 1 else self.path.with_suffix(f".{i-1}")
|
|
58
|
-
dst = self.path.with_suffix(f".{i}")
|
|
59
|
-
if src.exists():
|
|
60
|
-
if dst.exists():
|
|
61
|
-
dst.unlink()
|
|
62
|
-
src.rename(dst)
|
|
63
|
-
self._open()
|
|
64
|
-
|
|
65
|
-
def emit(self, message: str, level: LogLevel):
|
|
66
|
-
if self.level and level < self.level:
|
|
67
|
-
return
|
|
68
|
-
self._file.write(message + "\n")
|
|
69
|
-
self._file.flush()
|
|
70
|
-
if self.max_bytes and self._file.tell() >= self.max_bytes:
|
|
71
|
-
self._rotate()
|
|
72
|
-
|
|
73
|
-
class NetworkHandler(Handler):
|
|
74
|
-
def __init__(self, host: str, port: int, use_https: bool = False, level: LogLevel = LogLevel.DEBUG):
|
|
75
|
-
super().__init__(level)
|
|
76
|
-
self.host = host
|
|
77
|
-
self.port = port
|
|
78
|
-
self.use_https = use_https
|
|
79
|
-
|
|
80
|
-
def emit(self, message: str, level: LogLevel):
|
|
81
|
-
if level < self.level:
|
|
82
|
-
return
|
|
83
|
-
if self.use_https:
|
|
84
|
-
conn = http.client.HTTPSConnection(self.host, self.port, timeout=5)
|
|
85
|
-
else:
|
|
86
|
-
conn = http.client.HTTPConnection(self.host, self.port, timeout=5)
|
|
87
|
-
try:
|
|
88
|
-
conn.request("POST", "/", body=message.encode(), headers={"Content-Type": "text/plain"})
|
|
89
|
-
conn.getresponse().read()
|
|
90
|
-
finally:
|
|
91
|
-
conn.close()
|
|
92
|
-
|
|
93
|
-
class TCPHandler(Handler):
|
|
94
|
-
def __init__(self, host: str, port: int, level: LogLevel = LogLevel.DEBUG):
|
|
95
|
-
super().__init__(level)
|
|
96
|
-
self.host = host
|
|
97
|
-
self.port = port
|
|
98
|
-
|
|
99
|
-
def emit(self, message: str, level: LogLevel):
|
|
100
|
-
if level < self.level:
|
|
101
|
-
return
|
|
102
|
-
with socket.create_connection((self.host, self.port), timeout=5) as sock:
|
|
103
|
-
sock.sendall(message.encode() + b"\n")
|
|
104
|
-
|
|
105
|
-
class JSONFileHandler(FileHandler):
|
|
106
|
-
def __init__(self, path: str, level: LogLevel = LogLevel.DEBUG, max_bytes: int = 0, backups: int = 0):
|
|
107
|
-
super().__init__(path, level, max_bytes, backups)
|
|
108
|
-
|
|
109
|
-
def emit(self, message: str, level: LogLevel):
|
|
110
|
-
# Expect message to be a JSON string or dict
|
|
111
|
-
if level < self.level:
|
|
112
|
-
return
|
|
113
|
-
import json
|
|
114
|
-
if isinstance(message, dict):
|
|
115
|
-
log_entry = json.dumps(message)
|
|
116
|
-
else:
|
|
117
|
-
log_entry = message
|
|
118
|
-
self._file.write(log_entry + "\n")
|
|
119
|
-
self._file.flush()
|
|
120
|
-
if self.max_bytes and self._file.tell() >= self.max_bytes:
|
|
121
|
-
self._rotate()
|
|
1
|
+
import os
|
|
2
|
+
import socket
|
|
3
|
+
import http.client
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from .levels import LogLevel
|
|
9
|
+
|
|
10
|
+
RESET = "\033[0m"
|
|
11
|
+
LEVEL_COLORS = {
|
|
12
|
+
LogLevel.TRACE: "\033[90m",
|
|
13
|
+
LogLevel.DEBUG: "\033[36m",
|
|
14
|
+
LogLevel.INFO: "\033[32m",
|
|
15
|
+
LogLevel.WARNING: "\033[33m",
|
|
16
|
+
LogLevel.ERROR: "\033[31m",
|
|
17
|
+
LogLevel.CRITICAL: "\033[41m\033[97m",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class Handler:
|
|
21
|
+
def __init__(self, level: LogLevel = LogLevel.DEBUG):
|
|
22
|
+
self.level = level
|
|
23
|
+
|
|
24
|
+
def emit(self, message: str, level: LogLevel):
|
|
25
|
+
raise NotImplementedError
|
|
26
|
+
|
|
27
|
+
class ConsoleHandler(Handler):
|
|
28
|
+
def __init__(self, stream=None, level: LogLevel = LogLevel.DEBUG):
|
|
29
|
+
super().__init__(level)
|
|
30
|
+
self.stream = stream or os.sys.stdout
|
|
31
|
+
|
|
32
|
+
def emit(self, message: str, level: LogLevel):
|
|
33
|
+
color = LEVEL_COLORS.get(level, "")
|
|
34
|
+
self.stream.write(f"{color}{message}{RESET}\n")
|
|
35
|
+
self.stream.flush()
|
|
36
|
+
|
|
37
|
+
class FileHandler(Handler):
|
|
38
|
+
def __init__(self, path: str, level: LogLevel = LogLevel.DEBUG, max_bytes: int = 0, backups: int = 0):
|
|
39
|
+
super().__init__(level)
|
|
40
|
+
self.path = Path(path)
|
|
41
|
+
self.max_bytes = max_bytes
|
|
42
|
+
self.backups = backups
|
|
43
|
+
self._open()
|
|
44
|
+
|
|
45
|
+
def _open(self):
|
|
46
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
47
|
+
self._file = open(self.path, "a", encoding="utf-8")
|
|
48
|
+
|
|
49
|
+
def _rotate(self):
|
|
50
|
+
if self.backups <= 0:
|
|
51
|
+
self._file.close()
|
|
52
|
+
self.path.unlink(missing_ok=True)
|
|
53
|
+
self._open()
|
|
54
|
+
return
|
|
55
|
+
self._file.close()
|
|
56
|
+
for i in range(self.backups, 0, -1):
|
|
57
|
+
src = self.path.with_suffix(f".{i}") if i == 1 else self.path.with_suffix(f".{i-1}")
|
|
58
|
+
dst = self.path.with_suffix(f".{i}")
|
|
59
|
+
if src.exists():
|
|
60
|
+
if dst.exists():
|
|
61
|
+
dst.unlink()
|
|
62
|
+
src.rename(dst)
|
|
63
|
+
self._open()
|
|
64
|
+
|
|
65
|
+
def emit(self, message: str, level: LogLevel):
|
|
66
|
+
if self.level and level < self.level:
|
|
67
|
+
return
|
|
68
|
+
self._file.write(message + "\n")
|
|
69
|
+
self._file.flush()
|
|
70
|
+
if self.max_bytes and self._file.tell() >= self.max_bytes:
|
|
71
|
+
self._rotate()
|
|
72
|
+
|
|
73
|
+
class NetworkHandler(Handler):
|
|
74
|
+
def __init__(self, host: str, port: int, use_https: bool = False, level: LogLevel = LogLevel.DEBUG):
|
|
75
|
+
super().__init__(level)
|
|
76
|
+
self.host = host
|
|
77
|
+
self.port = port
|
|
78
|
+
self.use_https = use_https
|
|
79
|
+
|
|
80
|
+
def emit(self, message: str, level: LogLevel):
|
|
81
|
+
if level < self.level:
|
|
82
|
+
return
|
|
83
|
+
if self.use_https:
|
|
84
|
+
conn = http.client.HTTPSConnection(self.host, self.port, timeout=5)
|
|
85
|
+
else:
|
|
86
|
+
conn = http.client.HTTPConnection(self.host, self.port, timeout=5)
|
|
87
|
+
try:
|
|
88
|
+
conn.request("POST", "/", body=message.encode(), headers={"Content-Type": "text/plain"})
|
|
89
|
+
conn.getresponse().read()
|
|
90
|
+
finally:
|
|
91
|
+
conn.close()
|
|
92
|
+
|
|
93
|
+
class TCPHandler(Handler):
|
|
94
|
+
def __init__(self, host: str, port: int, level: LogLevel = LogLevel.DEBUG):
|
|
95
|
+
super().__init__(level)
|
|
96
|
+
self.host = host
|
|
97
|
+
self.port = port
|
|
98
|
+
|
|
99
|
+
def emit(self, message: str, level: LogLevel):
|
|
100
|
+
if level < self.level:
|
|
101
|
+
return
|
|
102
|
+
with socket.create_connection((self.host, self.port), timeout=5) as sock:
|
|
103
|
+
sock.sendall(message.encode() + b"\n")
|
|
104
|
+
|
|
105
|
+
class JSONFileHandler(FileHandler):
|
|
106
|
+
def __init__(self, path: str, level: LogLevel = LogLevel.DEBUG, max_bytes: int = 0, backups: int = 0):
|
|
107
|
+
super().__init__(path, level, max_bytes, backups)
|
|
108
|
+
|
|
109
|
+
def emit(self, message: str, level: LogLevel):
|
|
110
|
+
# Expect message to be a JSON string or dict
|
|
111
|
+
if level < self.level:
|
|
112
|
+
return
|
|
113
|
+
import json
|
|
114
|
+
if isinstance(message, dict):
|
|
115
|
+
log_entry = json.dumps(message)
|
|
116
|
+
else:
|
|
117
|
+
log_entry = message
|
|
118
|
+
self._file.write(log_entry + "\n")
|
|
119
|
+
self._file.flush()
|
|
120
|
+
if self.max_bytes and self._file.tell() >= self.max_bytes:
|
|
121
|
+
self._rotate()
|
webscout/Litlogger/levels.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
from enum import IntEnum
|
|
2
|
-
|
|
3
|
-
class LogLevel(IntEnum):
|
|
4
|
-
TRACE = 10
|
|
5
|
-
DEBUG = 20
|
|
6
|
-
INFO = 30
|
|
7
|
-
WARNING = 40
|
|
8
|
-
ERROR = 50
|
|
9
|
-
CRITICAL = 60
|
|
10
|
-
|
|
11
|
-
@classmethod
|
|
12
|
-
def from_str(cls, level: str) -> "LogLevel":
|
|
13
|
-
return cls[level.upper()]
|
|
1
|
+
from enum import IntEnum
|
|
2
|
+
|
|
3
|
+
class LogLevel(IntEnum):
|
|
4
|
+
TRACE = 10
|
|
5
|
+
DEBUG = 20
|
|
6
|
+
INFO = 30
|
|
7
|
+
WARNING = 40
|
|
8
|
+
ERROR = 50
|
|
9
|
+
CRITICAL = 60
|
|
10
|
+
|
|
11
|
+
@classmethod
|
|
12
|
+
def from_str(cls, level: str) -> "LogLevel":
|
|
13
|
+
return cls[level.upper()]
|
webscout/Litlogger/logger.py
CHANGED
|
@@ -1,134 +1,134 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import sys
|
|
3
|
-
import traceback
|
|
4
|
-
from datetime import datetime
|
|
5
|
-
from typing import List, Optional
|
|
6
|
-
|
|
7
|
-
from .levels import LogLevel
|
|
8
|
-
from .formats import LogFormat
|
|
9
|
-
from .handlers import Handler, ConsoleHandler
|
|
10
|
-
|
|
11
|
-
class Logger:
|
|
12
|
-
def __init__(
|
|
13
|
-
self,
|
|
14
|
-
name: str = "LitLogger",
|
|
15
|
-
level: LogLevel = LogLevel.INFO,
|
|
16
|
-
handlers: Optional[List[Handler]] = None,
|
|
17
|
-
fmt: str = LogFormat.DEFAULT, # <--- use LogFormat.DEFAULT
|
|
18
|
-
async_mode: bool = False,
|
|
19
|
-
include_context: bool = False, # New flag to include thread/process info
|
|
20
|
-
):
|
|
21
|
-
import threading
|
|
22
|
-
import multiprocessing
|
|
23
|
-
|
|
24
|
-
self.name = name
|
|
25
|
-
self.level = level
|
|
26
|
-
self.format = fmt
|
|
27
|
-
self.async_mode = async_mode
|
|
28
|
-
self.include_context = include_context
|
|
29
|
-
self.handlers = handlers or [ConsoleHandler()]
|
|
30
|
-
self._thread = threading
|
|
31
|
-
self._multiprocessing = multiprocessing
|
|
32
|
-
|
|
33
|
-
def _format(self, level: LogLevel, message: str) -> str:
|
|
34
|
-
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
35
|
-
if self.include_context:
|
|
36
|
-
thread_name = self._thread.current_thread().name
|
|
37
|
-
process_id = self._multiprocessing.current_process().pid
|
|
38
|
-
# Check if format is JSON format
|
|
39
|
-
if self.format.strip().startswith('{') and self.format.strip().endswith('}'):
|
|
40
|
-
# Format as JSON string with extra fields
|
|
41
|
-
return self.format.format(
|
|
42
|
-
time=now,
|
|
43
|
-
level=level.name,
|
|
44
|
-
name=self.name,
|
|
45
|
-
message=message,
|
|
46
|
-
thread=thread_name,
|
|
47
|
-
process=process_id
|
|
48
|
-
)
|
|
49
|
-
else:
|
|
50
|
-
# For non-JSON formats, add thread and process info if placeholders exist
|
|
51
|
-
try:
|
|
52
|
-
return self.format.format(
|
|
53
|
-
time=now,
|
|
54
|
-
level=level.name,
|
|
55
|
-
name=self.name,
|
|
56
|
-
message=message,
|
|
57
|
-
thread=thread_name,
|
|
58
|
-
process=process_id
|
|
59
|
-
)
|
|
60
|
-
except KeyError:
|
|
61
|
-
# If thread/process placeholders not in format, append them manually
|
|
62
|
-
base = self.format.format(time=now, level=level.name, name=self.name, message=message)
|
|
63
|
-
return f"{base} | Thread: {thread_name} | Process: {process_id}"
|
|
64
|
-
else:
|
|
65
|
-
return self.format.format(time=now, level=level.name, name=self.name, message=message)
|
|
66
|
-
|
|
67
|
-
def set_format(self, fmt: str, include_context: bool = False):
|
|
68
|
-
"""Dynamically change the log format and context inclusion."""
|
|
69
|
-
self.format = fmt
|
|
70
|
-
self.include_context = include_context
|
|
71
|
-
|
|
72
|
-
def _should_log(self, level: LogLevel) -> bool:
|
|
73
|
-
return level >= self.level
|
|
74
|
-
|
|
75
|
-
async def _log_async(self, level: LogLevel, message: str):
|
|
76
|
-
if not self._should_log(level):
|
|
77
|
-
return
|
|
78
|
-
record = self._format(level, message)
|
|
79
|
-
tasks = []
|
|
80
|
-
for h in self.handlers:
|
|
81
|
-
if level >= h.level:
|
|
82
|
-
if asyncio.iscoroutinefunction(h.emit):
|
|
83
|
-
tasks.append(h.emit(record, level))
|
|
84
|
-
else:
|
|
85
|
-
tasks.append(asyncio.to_thread(h.emit, record, level))
|
|
86
|
-
if tasks:
|
|
87
|
-
await asyncio.gather(*tasks)
|
|
88
|
-
|
|
89
|
-
def _log(self, level: LogLevel, message: str):
|
|
90
|
-
if not self._should_log(level):
|
|
91
|
-
return
|
|
92
|
-
record = self._format(level, message)
|
|
93
|
-
for h in self.handlers:
|
|
94
|
-
if level >= h.level:
|
|
95
|
-
h.emit(record, level)
|
|
96
|
-
|
|
97
|
-
def log(self, level: LogLevel, message: str):
|
|
98
|
-
if self.async_mode:
|
|
99
|
-
loop = asyncio.get_event_loop()
|
|
100
|
-
if loop.is_running():
|
|
101
|
-
return asyncio.create_task(self._log_async(level, message))
|
|
102
|
-
return loop.run_until_complete(self._log_async(level, message))
|
|
103
|
-
self._log(level, message)
|
|
104
|
-
|
|
105
|
-
def trace(self, message: str):
|
|
106
|
-
self.log(LogLevel.TRACE, message)
|
|
107
|
-
|
|
108
|
-
def debug(self, message: str):
|
|
109
|
-
self.log(LogLevel.DEBUG, message)
|
|
110
|
-
|
|
111
|
-
def info(self, message: str):
|
|
112
|
-
self.log(LogLevel.INFO, message)
|
|
113
|
-
|
|
114
|
-
def warning(self, message: str):
|
|
115
|
-
self.log(LogLevel.WARNING, message)
|
|
116
|
-
|
|
117
|
-
def error(self, message: str):
|
|
118
|
-
self.log(LogLevel.ERROR, message)
|
|
119
|
-
|
|
120
|
-
def critical(self, message: str):
|
|
121
|
-
self.log(LogLevel.CRITICAL, message)
|
|
122
|
-
|
|
123
|
-
def exception(self, message: str):
|
|
124
|
-
exc = sys.exc_info()
|
|
125
|
-
formatted = f"{message}\n" + "".join(traceback.format_exception(*exc))
|
|
126
|
-
self.error(formatted)
|
|
127
|
-
|
|
128
|
-
def __enter__(self):
|
|
129
|
-
return self
|
|
130
|
-
|
|
131
|
-
def __exit__(self, exc_type, exc, tb):
|
|
132
|
-
if exc_type:
|
|
133
|
-
self.exception(str(exc))
|
|
134
|
-
return False
|
|
1
|
+
import asyncio
|
|
2
|
+
import sys
|
|
3
|
+
import traceback
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import List, Optional
|
|
6
|
+
|
|
7
|
+
from .levels import LogLevel
|
|
8
|
+
from .formats import LogFormat
|
|
9
|
+
from .handlers import Handler, ConsoleHandler
|
|
10
|
+
|
|
11
|
+
class Logger:
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
name: str = "LitLogger",
|
|
15
|
+
level: LogLevel = LogLevel.INFO,
|
|
16
|
+
handlers: Optional[List[Handler]] = None,
|
|
17
|
+
fmt: str = LogFormat.DEFAULT, # <--- use LogFormat.DEFAULT
|
|
18
|
+
async_mode: bool = False,
|
|
19
|
+
include_context: bool = False, # New flag to include thread/process info
|
|
20
|
+
):
|
|
21
|
+
import threading
|
|
22
|
+
import multiprocessing
|
|
23
|
+
|
|
24
|
+
self.name = name
|
|
25
|
+
self.level = level
|
|
26
|
+
self.format = fmt
|
|
27
|
+
self.async_mode = async_mode
|
|
28
|
+
self.include_context = include_context
|
|
29
|
+
self.handlers = handlers or [ConsoleHandler()]
|
|
30
|
+
self._thread = threading
|
|
31
|
+
self._multiprocessing = multiprocessing
|
|
32
|
+
|
|
33
|
+
def _format(self, level: LogLevel, message: str) -> str:
|
|
34
|
+
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
35
|
+
if self.include_context:
|
|
36
|
+
thread_name = self._thread.current_thread().name
|
|
37
|
+
process_id = self._multiprocessing.current_process().pid
|
|
38
|
+
# Check if format is JSON format
|
|
39
|
+
if self.format.strip().startswith('{') and self.format.strip().endswith('}'):
|
|
40
|
+
# Format as JSON string with extra fields
|
|
41
|
+
return self.format.format(
|
|
42
|
+
time=now,
|
|
43
|
+
level=level.name,
|
|
44
|
+
name=self.name,
|
|
45
|
+
message=message,
|
|
46
|
+
thread=thread_name,
|
|
47
|
+
process=process_id
|
|
48
|
+
)
|
|
49
|
+
else:
|
|
50
|
+
# For non-JSON formats, add thread and process info if placeholders exist
|
|
51
|
+
try:
|
|
52
|
+
return self.format.format(
|
|
53
|
+
time=now,
|
|
54
|
+
level=level.name,
|
|
55
|
+
name=self.name,
|
|
56
|
+
message=message,
|
|
57
|
+
thread=thread_name,
|
|
58
|
+
process=process_id
|
|
59
|
+
)
|
|
60
|
+
except KeyError:
|
|
61
|
+
# If thread/process placeholders not in format, append them manually
|
|
62
|
+
base = self.format.format(time=now, level=level.name, name=self.name, message=message)
|
|
63
|
+
return f"{base} | Thread: {thread_name} | Process: {process_id}"
|
|
64
|
+
else:
|
|
65
|
+
return self.format.format(time=now, level=level.name, name=self.name, message=message)
|
|
66
|
+
|
|
67
|
+
def set_format(self, fmt: str, include_context: bool = False):
|
|
68
|
+
"""Dynamically change the log format and context inclusion."""
|
|
69
|
+
self.format = fmt
|
|
70
|
+
self.include_context = include_context
|
|
71
|
+
|
|
72
|
+
def _should_log(self, level: LogLevel) -> bool:
|
|
73
|
+
return level >= self.level
|
|
74
|
+
|
|
75
|
+
async def _log_async(self, level: LogLevel, message: str):
|
|
76
|
+
if not self._should_log(level):
|
|
77
|
+
return
|
|
78
|
+
record = self._format(level, message)
|
|
79
|
+
tasks = []
|
|
80
|
+
for h in self.handlers:
|
|
81
|
+
if level >= h.level:
|
|
82
|
+
if asyncio.iscoroutinefunction(h.emit):
|
|
83
|
+
tasks.append(h.emit(record, level))
|
|
84
|
+
else:
|
|
85
|
+
tasks.append(asyncio.to_thread(h.emit, record, level))
|
|
86
|
+
if tasks:
|
|
87
|
+
await asyncio.gather(*tasks)
|
|
88
|
+
|
|
89
|
+
def _log(self, level: LogLevel, message: str):
|
|
90
|
+
if not self._should_log(level):
|
|
91
|
+
return
|
|
92
|
+
record = self._format(level, message)
|
|
93
|
+
for h in self.handlers:
|
|
94
|
+
if level >= h.level:
|
|
95
|
+
h.emit(record, level)
|
|
96
|
+
|
|
97
|
+
def log(self, level: LogLevel, message: str):
|
|
98
|
+
if self.async_mode:
|
|
99
|
+
loop = asyncio.get_event_loop()
|
|
100
|
+
if loop.is_running():
|
|
101
|
+
return asyncio.create_task(self._log_async(level, message))
|
|
102
|
+
return loop.run_until_complete(self._log_async(level, message))
|
|
103
|
+
self._log(level, message)
|
|
104
|
+
|
|
105
|
+
def trace(self, message: str):
|
|
106
|
+
self.log(LogLevel.TRACE, message)
|
|
107
|
+
|
|
108
|
+
def debug(self, message: str):
|
|
109
|
+
self.log(LogLevel.DEBUG, message)
|
|
110
|
+
|
|
111
|
+
def info(self, message: str):
|
|
112
|
+
self.log(LogLevel.INFO, message)
|
|
113
|
+
|
|
114
|
+
def warning(self, message: str):
|
|
115
|
+
self.log(LogLevel.WARNING, message)
|
|
116
|
+
|
|
117
|
+
def error(self, message: str):
|
|
118
|
+
self.log(LogLevel.ERROR, message)
|
|
119
|
+
|
|
120
|
+
def critical(self, message: str):
|
|
121
|
+
self.log(LogLevel.CRITICAL, message)
|
|
122
|
+
|
|
123
|
+
def exception(self, message: str):
|
|
124
|
+
exc = sys.exc_info()
|
|
125
|
+
formatted = f"{message}\n" + "".join(traceback.format_exception(*exc))
|
|
126
|
+
self.error(formatted)
|
|
127
|
+
|
|
128
|
+
def __enter__(self):
|
|
129
|
+
return self
|
|
130
|
+
|
|
131
|
+
def __exit__(self, exc_type, exc, tb):
|
|
132
|
+
if exc_type:
|
|
133
|
+
self.exception(str(exc))
|
|
134
|
+
return False
|