lang updates and cache functionality for LogHandler

This commit is contained in:
Eric 2022-09-15 12:40:56 -07:00
parent 3816f1dcb9
commit d3f3679473
No known key found for this signature in database
GPG Key ID: 89C93F0F8D6D5A98
10 changed files with 152 additions and 79 deletions

View File

@ -420,8 +420,8 @@
"assets/build/ba_data/audio/zoeOw.ogg": "https://files.ballistica.net/cache/ba1/60/ad/38269b7f1c7dc20cb9a506cd0681",
"assets/build/ba_data/audio/zoePickup01.ogg": "https://files.ballistica.net/cache/ba1/72/85/d6fc4d16b7081d91fba2850b5b10",
"assets/build/ba_data/audio/zoeScream01.ogg": "https://files.ballistica.net/cache/ba1/e9/ae/1d674d0c086eaa0bd1c3b1db0505",
"assets/build/ba_data/data/langdata.json": "https://files.ballistica.net/cache/ba1/e8/42/a43c158be7fa45f2c0c3d4b84a1f",
"assets/build/ba_data/data/languages/arabic.json": "https://files.ballistica.net/cache/ba1/5b/cf/4501b151257c3d8d6ee8d0497d14",
"assets/build/ba_data/data/langdata.json": "https://files.ballistica.net/cache/ba1/d4/64/6fff42a428e5c775795c081474e6",
"assets/build/ba_data/data/languages/arabic.json": "https://files.ballistica.net/cache/ba1/e2/24/5e7ea9ca5c9de4d3b7a28e53564d",
"assets/build/ba_data/data/languages/belarussian.json": "https://files.ballistica.net/cache/ba1/61/03/89070ca765e06da3a419a579f503",
"assets/build/ba_data/data/languages/chinese.json": "https://files.ballistica.net/cache/ba1/f8/15/e1a2fa38697417bcf2cf19cd34ef",
"assets/build/ba_data/data/languages/chinesetraditional.json": "https://files.ballistica.net/cache/ba1/44/aa/c12568afb4558dc7f9f2fa155467",
@ -431,17 +431,17 @@
"assets/build/ba_data/data/languages/dutch.json": "https://files.ballistica.net/cache/ba1/68/93/da8e9874f41a786edf52ba4ccaad",
"assets/build/ba_data/data/languages/english.json": "https://files.ballistica.net/cache/ba1/72/80/d6395c8a168558750c0d79ce769b",
"assets/build/ba_data/data/languages/esperanto.json": "https://files.ballistica.net/cache/ba1/4c/c7/0184b8178869d1a3827a1bfcd5bb",
"assets/build/ba_data/data/languages/filipino.json": "https://files.ballistica.net/cache/ba1/8f/73/093120ae2241d8f4b899ccda2d75",
"assets/build/ba_data/data/languages/french.json": "https://files.ballistica.net/cache/ba1/25/65/1cb03566e73811fc6e1b841d9072",
"assets/build/ba_data/data/languages/filipino.json": "https://files.ballistica.net/cache/ba1/e9/07/b2dc862601bcd70701b083d43279",
"assets/build/ba_data/data/languages/french.json": "https://files.ballistica.net/cache/ba1/2e/48/b0a8fafc5e5436e99d9a3d697d23",
"assets/build/ba_data/data/languages/german.json": "https://files.ballistica.net/cache/ba1/ef/e6/d4909f571d7473fd04055728490e",
"assets/build/ba_data/data/languages/gibberish.json": "https://files.ballistica.net/cache/ba1/8a/2a/b2bc00eed0608b2199b2bc379b2e",
"assets/build/ba_data/data/languages/greek.json": "https://files.ballistica.net/cache/ba1/82/eb/37ff44af76812097f9c98f05c730",
"assets/build/ba_data/data/languages/hindi.json": "https://files.ballistica.net/cache/ba1/50/e8/837be1324c8128507b3df89b689f",
"assets/build/ba_data/data/languages/hindi.json": "https://files.ballistica.net/cache/ba1/91/98/42701cd595c2f70b7484614a8f49",
"assets/build/ba_data/data/languages/hungarian.json": "https://files.ballistica.net/cache/ba1/d8/f2/aa16bc336bd7660cc86c3264bfc4",
"assets/build/ba_data/data/languages/indonesian.json": "https://files.ballistica.net/cache/ba1/e3/85/14e57e3f49505e5a190daf7fe276",
"assets/build/ba_data/data/languages/indonesian.json": "https://files.ballistica.net/cache/ba1/23/3b/26e9be528460af952a11e98c3b68",
"assets/build/ba_data/data/languages/italian.json": "https://files.ballistica.net/cache/ba1/1a/10/9563348e729d1e5c8ae8c9cbc1f2",
"assets/build/ba_data/data/languages/korean.json": "https://files.ballistica.net/cache/ba1/a8/e9/171a904f1331fdb7b1918a0f2598",
"assets/build/ba_data/data/languages/persian.json": "https://files.ballistica.net/cache/ba1/83/4a/ec10142ac479bf8d80455b47a62b",
"assets/build/ba_data/data/languages/persian.json": "https://files.ballistica.net/cache/ba1/df/b1/b2c9ebaad5e873ebedd365726d3d",
"assets/build/ba_data/data/languages/polish.json": "https://files.ballistica.net/cache/ba1/19/e9/59c891b1fb85f3ba9f19283c233d",
"assets/build/ba_data/data/languages/portuguese.json": "https://files.ballistica.net/cache/ba1/da/95/36797ec53a697a04e55b225a701d",
"assets/build/ba_data/data/languages/romanian.json": "https://files.ballistica.net/cache/ba1/d7/06/9d70642d0a4d1e3b1c2149d7a17c",
@ -452,7 +452,7 @@
"assets/build/ba_data/data/languages/swedish.json": "https://files.ballistica.net/cache/ba1/91/0a/35c4baf539d5951fc03a794c0e0b",
"assets/build/ba_data/data/languages/tamil.json": "https://files.ballistica.net/cache/ba1/94/1a/533bc718e676191bafc25e2dc98f",
"assets/build/ba_data/data/languages/thai.json": "https://files.ballistica.net/cache/ba1/f7/df/7ba5f99c5c2c4c86fc0503fcf0b7",
"assets/build/ba_data/data/languages/turkish.json": "https://files.ballistica.net/cache/ba1/14/a0/783cc6da2d122e9a7482c6a5ef8c",
"assets/build/ba_data/data/languages/turkish.json": "https://files.ballistica.net/cache/ba1/9a/90/8e2ed626def09f88c3b9ab5215a3",
"assets/build/ba_data/data/languages/ukrainian.json": "https://files.ballistica.net/cache/ba1/ab/35/644e4239cfa62a597a905412b90c",
"assets/build/ba_data/data/languages/venetian.json": "https://files.ballistica.net/cache/ba1/53/9e/068074156b38bab7f732977a4031",
"assets/build/ba_data/data/languages/vietnamese.json": "https://files.ballistica.net/cache/ba1/25/13/b64b849fc9fedcc18d81f6e08c4d",
@ -3995,26 +3995,26 @@
"assets/src/ba_data/python/ba/_generated/__init__.py": "https://files.ballistica.net/cache/ba1/ee/e8/cad05aa531c7faf7ff7b96db7f6e",
"assets/src/ba_data/python/ba/_generated/enums.py": "https://files.ballistica.net/cache/ba1/b2/e5/0ee0561e16257a32830645239f34",
"ballisticacore-windows/Generic/BallisticaCore.ico": "https://files.ballistica.net/cache/ba1/89/c0/e32c7d2a35dc9aef57cc73b0911a",
"build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/15/b2/0744afc264f1e55a5944bf8ae964",
"build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/8e/98/ea10bd892f89c7ba5aec76721667",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/aa/e4/7c73515c9044e051a5d07cb1e964",
"build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ba/37/245e7c3dc79588d73a57f9f08b55",
"build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/ad/28/034d014eea6aeba4b67d51cfc262",
"build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/77/df/c85dfe5cb062b6925da7df8e740c",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/58/6b/058b05227950f5d83f23b01617d1",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/bd/31/02ecb4d8c3bea8eb68d92befdd9d",
"build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/aa/9d/96555101ebf8b14223b7639cea5b",
"build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/1d/9a/16feda78b815cbed47b89983c48e",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/b0/31/f60dc64681a35406595ed4836f0a",
"build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/b5/44/eb4645945794253446c55dc682bc",
"build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/43/56/592cd419bfad9cbbb6e3f57c1007",
"build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/c6/55/0dfcc3d90a9cee9212e2e10a91b1",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/19/c6/994729e095b54e6963ac2d4bbd10",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ce/21/77cd76cd1b0307ae8cc7038152e7",
"build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/a9/e6/23835561d55b6c1d1862217fc115",
"build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/21/3d/e0a1e20ae12dd7b5ba1d61aed074",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/be/b5/fc827900f411576b992fc15a25fc",
"build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/6f/51/d6a0951b6ab9122dd99ef33d4427",
"build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/f3/e1/0ee9ade5e9943dc4749aa4cc2182",
"build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/32/4e/a7c1b096c62864641a59e65e7778",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/6a/f4/8b86611cbaa7237c8a52e8fe6428",
"build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/13/f5/fa3c97269613316994f0c2134860",
"build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/f8/7a/87877003ee3a13ff7cbbf1ec76a2",
"build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/81/0c/708a16fff55ee37338b2d87f7620",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/4a/91/89523b8f155261e0045a6bb6e277",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/a9/04/da76c8e848852fef1e58577f59e3",
"build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/dd/ac/33aed9cc1396ccba2111dfed02f6",
"build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/1a/5a/0f1e671137a2ec60c2df8e95e078",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/92/50/ec5fbb3f8b9f4a60934f2dacf180",
"build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/7c/77/a430bf182d5210792716b6dbd8ee",
"build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/ef/6b/de6c811fa70bf4d5d1cdfe49eaae",
"build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/92/e0/b9b1053231d7323d185298f8caaa",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/08/ef/c70efcd89a666758437f0736d956",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/9d/94/1f0d32c943b64ff558f28ad89653",
"build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/a6/fb/a6e6a78d7b04d547c9c716ff0a8e",
"build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/5a/b0/d0e087fcdf1756a0b9b5bf805f10",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/34/fb/e6c9fb0e20af0ba3376ca2abba29",
"build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/b1/8b/5ba626c39ef586f3ba4bfd508181",
"build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d5/f6/d62e6e6d5e7fe1945f08ccbb9a8f",
"build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/02/c5/44d0082442b06153a7d7dce4c8ce",
"build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d0/00/61378fb26ddcbf023fd3c40e4ffb",
@ -4031,14 +4031,14 @@
"build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ff/26/da4a58abecf5d9275477eaec0c17",
"build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/62/5d/8658a206b8c9b741be2422162784",
"build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/fc/57/d59920e098d23a2d150c899cde29",
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/fb/8e/3bb1c858451a447f1a102d77c281",
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/ae/ad/13ae735e45d31d2944c89e8bdcec",
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/e8/3e/6c2546278ad0428965a05c9bd536",
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/d4/e5/0fe217cac5837cf663c7c7f1aa92",
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/ea/38/a5082d73202113900166a1ebd15f",
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/ba/1c/16df69d156e23ab29e2c84465bd7",
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/9e/b0/91b2e0b1dbd4541543f3f147bc06",
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/c1/04/ad892d77317be76bc8fa035f9e86",
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/39/a1/bce547622da143fcf9ea970cd3ab",
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/d6/f3/8ca052a667342e316fa09655bc51",
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/e0/b4/99c403ccbfd0c4b9b071e656d480",
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/11/a0/e06103dae63a4648850bd089aacb",
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/77/17/99eba0c7d42a9452306f7927ae4e",
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/92/da/2faf9031e7c5ba5a99dd9e0c609d",
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/64/2a/1b30ee7b4e8238ec2ea16bcacdeb",
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/41/92/35b689da32ee45a4e7483feae8f8",
"src/ballistica/generated/python_embedded/binding.inc": "https://files.ballistica.net/cache/ba1/c0/32/b7907e3859a5c5013a3d97b6b523",
"src/ballistica/generated/python_embedded/bootstrap.inc": "https://files.ballistica.net/cache/ba1/2d/4f/f4fe67827f36cd59cd5193333a02",
"src/ballistica/generated/python_embedded/bootstrap_monolithic.inc": "https://files.ballistica.net/cache/ba1/ef/c1/aa5f1aa10af89f5c0b1e616355fd"

View File

@ -1,4 +1,4 @@
### 1.7.7 (build 20856, api 7, 2022-09-15)
### 1.7.7 (build 20857, api 7, 2022-09-15)
- Added `ba.app.meta.load_exported_classes()` for loading classes discovered by the meta subsystem cleanly in a background thread.
- Improved logging of missing playlist game types.
- Some ba.Lstr functionality can now be used in background threads.

View File

@ -1 +1 @@
76251027805752156826413428926087661089
41453813326605937968225345803585801012

View File

@ -2518,11 +2518,12 @@ def screenmessage(message: str | ba.Lstr,
Category: **General Utility Functions**
If 'top' is True, the message will go to the top message area.
For 'top' messages, 'image' can be a texture to display alongside the
message.
If 'log' is True, the message will also be printed to the output log
'clients' can be a list of client-ids the message should be sent to,
or None to specify that everyone should receive it.
For 'top' messages, 'image' must be a dict containing 'texture'
and 'tint_texture' textures and 'tint_color' and 'tint2_color'
colors. This defines an icon to display alongside the message.
If 'log' is True, the message will also be submitted to the log.
'clients' can be a list of client-ids the message should be sent
to, or None to specify that everyone should receive it.
If 'transient' is True, the message will not be included in the
game-stream and thus will not show up when viewing replays.
Currently the 'clients' option only works for transient messages.

View File

@ -35,7 +35,8 @@ def bootstrap() -> None:
log_handler = setup_logging(log_path=None,
level=LogLevel.DEBUG,
suppress_non_root_debug=True,
log_stdout_stderr=True)
log_stdout_stderr=True,
cache_size_limit=1024 * 1024)
log_handler.add_callback(_on_log)
@ -43,7 +44,7 @@ def bootstrap() -> None:
# Give a soft warning if we're being used with a different binary
# version than we expect.
expected_build = 20856
expected_build = 20857
running_build: int = env['build_number']
if running_build != expected_build:
print(

View File

@ -32,7 +32,7 @@
namespace ballistica {
// These are set automatically via script; don't modify them here.
const int kAppBuildNumber = 20856;
const int kAppBuildNumber = 20857;
const char* kAppVersion = "1.7.7";
// Our standalone globals.

View File

@ -19,9 +19,9 @@ namespace ballistica {
const int kMaxPartyNameCombinedSize = 25;
/// The Game Module generally runs on a dedicated thread; it manages
/// all game logic, builds frame_defs to send to the graphics-server for
/// rendering, etc.
/// The logic subsystem of the app. This runs on a dedicated thread
/// and is where high level app logic happens. Much app functionality
/// including UI calls must be run on the logic thread.
class Logic {
public:
Logic();

View File

@ -988,14 +988,12 @@ auto PythonMethodsApp::GetMethods() -> std::vector<PyMethodDef> {
"Category: **General Utility Functions**\n"
"\n"
"If 'top' is True, the message will go to the top message area.\n"
"For 'top' messages, 'image' can be a texture to display alongside "
"the\n"
"message.\n"
"If 'log' is True, the message will also be printed to the output "
"log\n"
"'clients' can be a list of client-ids the message should be sent "
"to,\n"
"or None to specify that everyone should receive it.\n"
"For 'top' messages, 'image' must be a dict containing 'texture'\n"
"and 'tint_texture' textures and 'tint_color' and 'tint2_color'\n"
"colors. This defines an icon to display alongside the message.\n"
"If 'log' is True, the message will also be submitted to the log.\n"
"'clients' can be a list of client-ids the message should be sent\n"
"to, or None to specify that everyone should receive it.\n"
"If 'transient' is True, the message will not be included in the\n"
"game-stream and thus will not show up when viewing replays.\n"
"Currently the 'clients' option only works for transient messages."},

View File

@ -70,7 +70,7 @@ namespace ballistica {
auto Python::LoggingCall(LogLevel loglevel, const std::string& msg) -> void {
// If we've not yet captured our Python logging calls, stash this call away.
// We'll submit all accumulated entries after we bootstrap python.
// We'll submit all accumulated entries after we bootstrap Python.
if (!objexists(ObjID::kLoggingCriticalCall)) {
std::scoped_lock lock(early_log_lock_);
early_logs_.emplace_back(std::make_pair(loglevel, msg));

View File

@ -67,6 +67,21 @@ class LogEntry:
time: Annotated[datetime.datetime, IOAttrs('t')]
@ioprepped
@dataclass
class LogArchive:
"""Info and data for a log."""
# Total number of entries submitted to the log.
log_size: Annotated[int, IOAttrs('t')]
# Offset for the entries contained here.
# (10 means our first entry is the 10th in the log, etc.)
start_index: Annotated[int, IOAttrs('c')]
entries: Annotated[list[LogEntry], IOAttrs('e')]
class LogHandler(logging.Handler):
"""Fancy-pants handler for logging output.
@ -83,7 +98,8 @@ class LogHandler(logging.Handler):
def __init__(self,
path: str | Path | None,
echofile: TextIO | None,
suppress_non_root_debug: bool = False):
suppress_non_root_debug: bool = False,
cache_size_limit: int = 0):
super().__init__()
# pylint: disable=consider-using-with
self._file = (None
@ -97,14 +113,20 @@ class LogHandler(logging.Handler):
'stdout': None,
'stderr': None
}
self._cache_size = 0
assert cache_size_limit >= 0
self._cache_size_limit = cache_size_limit
self._cache: list[tuple[int, LogEntry]] = []
self._cache_index_offset = 0
self._cache_lock = Lock()
self._printed_callback_error = False
self._thread_bootstrapped = False
self._thread = Thread(target=self._thread_main, daemon=True)
self._thread.start()
# Spin until our thread has set up its basic stuff;
# otherwise we could wind up trying to push stuff to our
# event loop before the loop exists.
# Spin until our thread is up and running; otherwise we could
# wind up trying to push stuff to our event loop before the
# loop exists.
while not self._thread_bootstrapped:
time.sleep(0.001)
@ -124,6 +146,37 @@ class LogHandler(logging.Handler):
self._thread_bootstrapped = True
self._event_loop.run_forever()
def get_archive(self,
start_index: int = 0,
max_entries: int | None = None) -> LogArchive:
"""Build and return an archive of log entries.
This will only return entries that have been processed by the
background thread so may not include just-submitted logs.
Entries in the range [start_index:start_index+max_entries] that
are still in the cache will be returned. Be aware that this may
not be the full requested range.
"""
assert start_index >= 0
if max_entries is not None:
assert max_entries >= 0
with self._cache_lock:
# Transform start_index to our present cache space.
start_index -= self._cache_index_offset
# Calc end-index in our present cache space.
end_index = (len(self._cache)
if max_entries is None else start_index + max_entries)
# Clamp both indexes to both ends of our present space.
start_index = max(0, min(start_index, len(self._cache)))
end_index = max(0, min(end_index, len(self._cache)))
return LogArchive(
log_size=self._cache_index_offset + len(self._cache),
start_index=start_index + self._cache_index_offset,
entries=[e[1] for e in self._cache[start_index:end_index]])
def emit(self, record: logging.LogRecord) -> None:
# Called by logging to send us records.
# We simply package them up and ship them to our thread.
@ -143,22 +196,23 @@ class LogHandler(logging.Handler):
# didn't expect to be stringified.
msg = self.format(record)
# Also print pretty colored output to our echo file (generally
# stderr). We do this part here instead of in our bg thread
# because the delay can throw off command line prompts or make
# tight debugging harder.
# Also immediately print pretty colored output to our echo file
# (generally stderr). We do this part here instead of in our bg
# thread because the delay can throw off command line prompts or
# make tight debugging harder.
if self._echofile is not None:
cbegin: str
cend: str
cbegin, cend = LEVELNO_COLOR_CODES.get(record.levelno, ('', ''))
self._echofile.write(f'{cbegin}{msg}{cend}\n')
ends = LEVELNO_COLOR_CODES.get(record.levelno)
if ends is not None:
self._echofile.write(f'{ends[0]}{msg}{ends[1]}\n')
else:
self._echofile.write(f'{msg}\n')
self._event_loop.call_soon_threadsafe(
tpartial(self._emit_in_loop, record.name, record.levelno,
tpartial(self._emit_in_thread, record.name, record.levelno,
record.created, msg))
def _emit_in_loop(self, name: str, levelno: int, created: float,
message: str) -> None:
def _emit_in_thread(self, name: str, levelno: int, created: float,
message: str) -> None:
try:
self._emit_entry(
LogEntry(name=name,
@ -174,9 +228,9 @@ class LogHandler(logging.Handler):
"""Send raw stdout/stderr output to the logger to be collated."""
self._event_loop.call_soon_threadsafe(
tpartial(self._file_write_in_loop, name, output))
tpartial(self._file_write_in_thread, name, output))
def _file_write_in_loop(self, name: str, output: str) -> None:
def _file_write_in_thread(self, name: str, output: str) -> None:
try:
assert name in ('stdout', 'stderr')
@ -222,9 +276,26 @@ class LogHandler(logging.Handler):
self._file_chunk_ship_task[name] = None
def _emit_entry(self, entry: LogEntry) -> None:
# This runs in our bg event loop thread and does most of the work.
assert current_thread() is self._thread
# Store to our cache.
if self._cache_size_limit > 0:
with self._cache_lock:
# Do a rough calc of how many bytes this entry consumes.
entry_size = sum(
sys.getsizeof(x)
for x in (entry, entry.name, entry.message, entry.level,
entry.time))
self._cache.append((entry_size, entry))
self._cache_size += entry_size
# Prune old until we are back at or under our limit.
while self._cache_size > self._cache_size_limit:
popped = self._cache.pop(0)
self._cache_size -= popped[0]
self._cache_index_offset += 1
# Pass to callbacks.
with self._callbacks_lock:
for call in self._callbacks:
try:
@ -271,11 +342,12 @@ class FileLogEcho:
def setup_logging(log_path: str | Path | None,
level: LogLevel,
suppress_non_root_debug: bool = False,
log_stdout_stderr: bool = False) -> LogHandler:
log_stdout_stderr: bool = False,
cache_size_limit: int = 0) -> LogHandler:
"""Set up our logging environment.
Returns the custom handler which can be used to fetch information
about logs that have passed through it. (worst log-levels, etc.).
about logs that have passed through it. (worst log-levels, caches, etc.).
"""
lmap = {
@ -295,7 +367,8 @@ def setup_logging(log_path: str | Path | None,
loghandler = LogHandler(
log_path,
echofile=sys.stderr if sys.stderr.isatty() else None,
suppress_non_root_debug=suppress_non_root_debug)
suppress_non_root_debug=suppress_non_root_debug,
cache_size_limit=cache_size_limit)
# Note: going ahead with force=True here so that we replace any
# existing logger. Though we warn if it looks like we are doing