- Shared singleton logger with per-level colorized output.
oncehelpers prevent duplicate log spam automatically.- Stackable progress bars that stay anchored while your logs flow freely.
- Sub-cell Unicode bar rasterization for smoother, more accurate terminal fills.
- Built-in styling for progress bar fills, colors, gradients, and head glyphs.
- Animated progress titles with a subtle sweeping highlight.
Set
LOGBAR_ANIMATION=0to disable the highlight animation. - Progress output throttling for reducing redraw churn in batch-heavy jobs.
Set
LOGBAR_PROGRESS_OUTPUT_INTERVAL=10to render every 10 logical updates instead of every update. - Column-aware table printer with spans, width hints, and
fitsizing. - Zero dependencies; works anywhere Python runs.
pip install logbarLogBar works out-of-the-box with CPython 3.8+ on Linux, macOS, and Windows terminals.
LogBar keeps progress bars, spinners, tables, and normal log lines readable in the same terminal session. Compared with traditional loggers, it lets long-running CLI programs show live status without flooding the screen with repeated status lines or breaking the flow of regular logs.
Main rendering APIs:
log.pb(...)for live progress barslog.spinner(...)for work with no fixed totallog.columns(...)for aligned table output
Examples:
from logbar import LogBar
log = LogBar.shared()
for _ in log.pb(range(5)).title("下载 📦").subtitle("phase 1"):
passjobs = ["scan", "parse", "index", "flush"]
pb = log.pb(jobs, output_interval=1).title("Indexing").manual()
for job in pb:
log.info("processing %s", job)
pb.subtitle(job).draw()cols = log.columns(
{"label": "task", "width": "fit"},
{"label": "status", "width": "fit"},
{"label": "detail", "width": "50%"},
)
cols.info.header()
cols.info("render", "active", "width and alignment stay terminal-aware")Experimental split-screen sessions:
from logbar import RegionScreenSession, rows
with RegionScreenSession.columns("left", rows("right_top", "right_bottom")) as ui:
left = ui.create_logger("left", supports_ansi=False)
right_top = ui.create_logger("right_top", supports_ansi=False)
right_bottom = ui.create_logger("right_bottom", supports_ansi=False)
left.setLevel("INFO")
right_top.setLevel("INFO")
right_bottom.setLevel("INFO")
left.info("download queue ready")
right_top.info("worker online")
right_bottom.set_footer_lines(["gpu warmup", "epoch 1/8"])Plain-text sketch:
+----------------------+----------------------+
| INFO download ... | INFO worker online |
| |----------------------|
| | gpu warmup |
| | epoch 1/8 |
+----------------------+----------------------+
import time
from logbar import LogBar
log = LogBar.shared()
log.info("hello from logbar")
log.info.once("this line shows once")
log.info.once("this line shows once") # silently skipped
for _ in log.pb(range(5)):
time.sleep(0.2)Sample output (colors omitted in plain-text view):
INFO hello from logbar
INFO this line shows once
INFO [###---------------] 20% (1/5)
The shared instance exposes the standard level helpers plus once variants:
log.debug("details...")
log.warn("disk space is low")
log.error("cannot connect to database")
log.critical.once("fuse blown, shutting down")Set a minimum output threshold per logger instance:
log.setLevel("WARN") # accepts DEBUG/INFO/WARN/ERROR/CRIT strings
log.setLevel("ERROR")
log.setLevel(LogBar.WARNING) # alias to logging.WARNINGTypical mixed-level output (Note: Markdown cannot display ANSI colors):
DEBUG model version=v2.9.1
WARN disk space is low (5%)
ERROR cannot connect to database
CRIT fuse blown, shutting down
Progress bars accept any iterable or integer total:
for item in log.pb(tasks):
process(item)
for _ in log.pb(500).title("Downloading"):
time.sleep(0.05)When a workload updates progress very frequently, throttle redraw churn globally or per bar:
for _ in log.pb(500, output_interval=10).title("Quantizing"):
time.sleep(0.01)output_interval=10 means LogBar will emit a fresh snapshot after roughly every 10 logical progress steps, while still forcing the last pending step to render before the bar closes. Set LOGBAR_PROGRESS_OUTPUT_INTERVAL=10 to apply the same default process-wide.
Manual mode gives full control when you need to interleave logging and redraws:
pb = log.pb(jobs).title("Processing").manual()
for job in pb:
log.info(f"starting {job}")
pb.subtitle(f"in-flight: {job}").draw()
run(job)
log.info(f"finished {job}")Progress bar snapshot (plain-text example):
INFO Processing [##########------------] 40% (8/20) in-flight: step-8
The bar always re-renders at the bottom, so log lines never overwrite your progress.
When the total work is unknown, log.spinner() provides a rolling indicator that redraws every 500 ms until closed:
with log.spinner("Loading model") as spinner:
load_weights()
spinner.subtitle("warming up")
warm_up()The rolling bar animates automatically while attached. Close it explicitly with spinner.close() if you are not using the context manager. Set LOGBAR_ANIMATION=0 to disable the title highlight sweep on progress labels.
LogBar keeps each progress bar on its own line and restacks them whenever they redraw. Later bars always appear closest to the live log output.
pb_fetch = log.pb(range(80)).title("Fetch").manual()
pb_train = log.pb(range(120)).title("Train").manual()
for _ in pb_fetch:
pb_fetch.draw()
time.sleep(0.01)
for _ in pb_train:
pb_train.draw()
time.sleep(0.01)
pb_train.close()
pb_fetch.close()Sample stacked output (plain-text view):
INFO Fetch [███████░░░░░░░░░░░░░░░░░░░░░░░░░░░░] | 58.8% 00:00:46 / 00:01:19
INFO Train [█████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░] | 37.5% 00:01:15 / 00:03:20
Pick from bundled palettes or create your own blocks and colors:
pb = log.pb(250)
pb.style('sunset') # bundled gradients: emerald_glow, sunset, ocean, matrix, mono
pb.fill('▓', empty='·') # override glyphs
pb.colors(fill=['#ff9500', '#ff2d55'], head='mint') # custom palette, optional head accent
pb.colors(empty='slate') # tint the empty track
pb.head('>', color='82') # custom head glyph + color indexProgressBar.available_styles() lists builtin styles, and you can register additional ones with ProgressBar.register_style(...) or switch defaults globally via ProgressBar.set_default_style(...). Custom colors accept ANSI escape codes, 256-color indexes (e.g. '82'), or hex strings ('#4c1d95').
For direct style registration and introspection, import the advanced style APIs from logbar.progress:
from logbar.progress import ProgressBar, ProgressStyle, progress_style_names
print(ProgressBar.available_styles())
print(progress_style_names())
ProgressBar.register_style(
ProgressStyle(
name="ice",
fill_char="■",
empty_char="·",
fill_colors=("#7dd3fc", "#38bdf8"),
gradient=True,
head_char=">",
)
)
ProgressBar.set_default_style("ice")Styled output (plain-text view with ANSI removed):
INFO Upload [▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉···········] | 62.0% 00:01:48 / 00:02:52
Use log.columns(...) to format aligned tables while logging data streams. Print the column header per context with cols.info.header() (or cols.warn.header(), etc.). Columns support spans and three width hints:
- character width:
"24" - percentage of the available log width:
"30%" - content-driven fit:
"fit"
cols = log.columns(
{"label": "tag", "width": "fit"},
{"label": "duration", "width": 8},
{"label": "message", "span": 2}
)
cols.info.header()
cols.info("startup", "1.2s", "ready", "subsystem online")
cols.info("alignment", "0.5s", "resizing", "fit width active")Sample table output (plain-text):
INFO +----------+----------+-----------------------------+------------------------------+
INFO | tag | duration | message | message |
INFO +----------+----------+-----------------------------+------------------------------+
INFO | startup | 1.2s | ready | subsystem online |
INFO +----------+----------+-----------------------------+------------------------------+
INFO | alignment| 0.5s | resizing | fit width active |
INFO +----------+----------+-----------------------------+------------------------------+
Notice how the tag column expands precisely to the longest value thanks to width="fit".
You can update column definitions at runtime:
cols.update({
"message": {"width": "40%"},
"duration": {"label": "time"}
})Useful column helpers:
cols.info.header()orcols.info.headers()prints the current border + header block.cols.info.simulate(...)recomputes widths without emitting a row.cols.update(...)changes labels, spans, or widths at runtime.cols.width()returns the current rendered table width, including borders.cols.widths,cols.padding, andcols.column_specsexpose the current layout.
The API mirrors common tqdm patterns while staying more Pythonic:
# tqdm
for n in tqdm.tqdm(range(1000)):
consume(n)
# logbar
for n in log.pb(range(1000)):
consume(n)Manual update comparison:
# tqdm manual mode
with tqdm.tqdm(total=len(items)) as pb:
for item in items:
handle(item)
pb.update()
# logbar manual redraw
with log.pb(items).manual() as pb:
for item in pb:
handle(item)
pb.draw()- Combine columns and progress bars by logging summaries at key checkpoints.
- Use
log.warn.once(...)to keep noisy health checks readable. - For multi-line messages, pre-format text and pass it as a single string; LogBar keeps borders intact.
LogBar.shared(override_logger=False)returns the process-wide shared logger.override_logger=Trueis useful in tests or embedded environments that replaced the activelogginglogger class.- Level methods:
debug,info,warn,error,critical. - Deduplicated level methods:
debug.once,info.once,warn.once,error.once,critical.once. setLevel(level)accepts strings like"INFO","WARN","CRIT", numeric levels, numeric strings, and constants such asLogBar.WARNING.pb(iterable_or_total, output_interval=None)creates and attaches a progress bar.spinner(title="", interval=0.5, tail_length=4)creates and attaches an indeterminate rolling progress bar.columns(..., cols=None, width=None, padding=2)creates a column printer.
log.pb(...) returns an attached ProgressBar. For direct imports, use:
from logbar.progress import ProgressBar, ProgressStyleCommon chainable methods:
title(text)andsubtitle(text)style(name_or_style)fill(fill_char, empty=None)colors(fill=None, empty=None, gradient=None, head=None)head(char=None, color=None)set(show_left_steps=None, left_steps_offset=None)output_interval(interval)mode(RenderMode)if you prefer explicit mode switching overauto()/manual()
Render and lifecycle control:
draw(force=False)renders the current snapshot immediately.auto()enables redraw-on-iteration mode.manual()disables automatic redraw so you can calldraw()yourself.attach(logger=None)attaches the bar to a logger.detach()detaches the bar without destroying the object.close()forces a final render if needed and removes the bar from the stack.step()returns the current iteration index andnext()advances once outside aforloop.
Style registry helpers:
ProgressBar.available_styles()ProgressBar.register_style(style)ProgressBar.set_default_style(style)ProgressBar.default_style()
log.spinner(...) returns a RollingProgressBar, which inherits from ProgressBar and adds:
pulse()to advance the spinner immediately between automatic ticks.intervalandtail_lengthconstructor arguments for animation speed and tail size.
log.columns(...) returns a ColumnsPrinter with per-level proxies:
cols.info(...),cols.warn(...),cols.error(...),cols.debug(...),cols.critical(...)cols.info.header()andcols.info.headers()for border + header emissioncols.info.simulate(...)for dry-run width growth without outputcols.update(...)for runtime schema changescols.width()for the current rendered width
