Usage Example

Vislog (Visual Logger) adds a visual effect to standard logging. It is useful for logging in a function with multiple nested blocks and can help you understand the program’s execution flow.

Create Visual Logger

[1]:
import json
import time
from vislog import VisLog, AlignEnum

logger = VisLog(name="vislog_demo")

See VisLog for how to constructing a new logger.

Just Logging

Just call .debug("message here"), .info(...), …, .critical(...) to log some message. The | pipe character is for nested log block.

[2]:
logger.info("hello");
[User 2024-06-16 15:06:18] | hello

Change the default pipe character

[3]:
logger.info("hello", pipe="+");
[User 2024-06-16 15:06:18] + hello

Ruler

Horizontal ruler, by default it is left aligned

[4]:
logger.ruler("start");
[User 2024-06-16 15:06:18] +----- start ------------------------------------------------------------------+

Ruler can be center aligned

[5]:
logger.ruler("start", align=AlignEnum.middle);
[User 2024-06-16 15:06:18] +----------------------------------- start ------------------------------------+

Ruler can be right aligned

[6]:
logger.ruler("start", align=AlignEnum.right);
[User 2024-06-16 15:06:18] +------------------------------------------------------------------ start -----+

Ruler has many option, you can customize the ruler character.

[7]:
logger.ruler("start", char="=");
[User 2024-06-16 15:06:18] +===== start ==================================================================+

You can customize corner character too.

[8]:
logger.ruler("start", corner="#");
[User 2024-06-16 15:06:19] #----- start ------------------------------------------------------------------#

Multiline Message

If the log message across multiple lines, it still looks nice.

[9]:
data = {
    "a": {
        "b": {
            "c": 3
        }
    }
}
logger.info(json.dumps(data, indent=4));
[User 2024-06-16 15:06:19] | {
[User 2024-06-16 15:06:19] |     "a": {
[User 2024-06-16 15:06:19] |         "b": {
[User 2024-06-16 15:06:19] |             "c": 3
[User 2024-06-16 15:06:19] |         }
[User 2024-06-16 15:06:19] |     }
[User 2024-06-16 15:06:19] | }

Nested Log Block

You can put logic blocks nesting each other to improve readability.

[10]:
logger.ruler("section 1")
logger.info("hello 1")

with logger.nested():
    logger.ruler("section 1.1")
    logger.info("hello 1.1")

    with logger.nested():
        logger.ruler("section 1.1.1")
        logger.info("hello 1.1.1")
        logger.ruler("section 1.1.1")

    logger.ruler("section 1.1")

logger.ruler("section 1");
[User 2024-06-16 15:06:20] +----- section 1 --------------------------------------------------------------+
[User 2024-06-16 15:06:20] | hello 1
[User 2024-06-16 15:06:20] | +----- section 1.1 ----------------------------------------------------------+
[User 2024-06-16 15:06:20] | | hello 1.1
[User 2024-06-16 15:06:20] | | +----- section 1.1.1 ------------------------------------------------------+
[User 2024-06-16 15:06:20] | | | hello 1.1.1
[User 2024-06-16 15:06:20] | | +----- section 1.1.1 ------------------------------------------------------+
[User 2024-06-16 15:06:20] | +----- section 1.1 ----------------------------------------------------------+
[User 2024-06-16 15:06:20] +----- section 1 --------------------------------------------------------------+

pretty_log Decorator

You don’t have to explicitly construct the nested log block. A decorator can help you. It will log a ruler when you enter and exit the function automatically. You can use pretty log decorator to wrap a function to provide this visual effect easily.

[11]:
@logger.pretty_log()
def func1():
    time.sleep(1)
    logger.info("run func1")

func1()
[User 2024-06-16 15:06:20] +----- Start func1() ----------------------------------------------------------+
[User 2024-06-16 15:06:20] |
[User 2024-06-16 15:06:21] | run func1
[User 2024-06-16 15:06:21] |
[User 2024-06-16 15:06:21] +----- End func1(), elapsed = 1.01 sec ----------------------------------------+

The default enter and exit ruler use the following string template "Start {func_name}()" and "Error {func_name}(), elapsed = {elapsed:.2f} sec", you can customize this.

[12]:
@logger.pretty_log(
    start_msg="Start {func_name}(name={name})",
    end_msg="End {func_name}(), elapsed = {elapsed:.2f} sec",
)
def func1(name: str):
    time.sleep(1)
    logger.info(f"run func1: name = {name}")

func1(name="Alice")
[User 2024-06-16 15:06:21] +----- Start func1(name=Alice) ------------------------------------------------+
[User 2024-06-16 15:06:21] |
[User 2024-06-16 15:06:22] | run func1: name = Alice
[User 2024-06-16 15:06:22] |
[User 2024-06-16 15:06:22] +----- End func1(), elapsed = 1.01 sec ----------------------------------------+

pretty_log decorator also automatically add a ruler when raises an error. You can customize the error ruler too.

[14]:
@logger.pretty_log(
    error_msg="OPS!",
)
def func1():
    time.sleep(1)
    raise ValueError("something wrong!")

func1()
[User 2024-06-16 15:06:34] +----- Start func1() ----------------------------------------------------------+
[User 2024-06-16 15:06:34] |
[User 2024-06-16 15:06:35] |
[User 2024-06-16 15:06:35] +----- OPS! -------------------------------------------------------------------+
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[14], line 8
      5     time.sleep(1)
      6     raise ValueError("something wrong!")
----> 8 func1()

File ~/Documents/GitHub/vislog-project/vislog/impl.py:655, in VisLog.pretty_log.<locals>.deco.<locals>.wrapper(*args, **kwargs)
    653     if nest == 0 and (pipe is not None):
    654         self._pipe_end(pipe, last_pipe)
--> 655     raise e
    657 et = datetime.utcnow()
    658 elapsed = (et - st).total_seconds()

File ~/Documents/GitHub/vislog-project/vislog/impl.py:632, in VisLog.pretty_log.<locals>.deco.<locals>.wrapper(*args, **kwargs)
    629 self.info("")
    631 try:
--> 632     result = func(*args, **kwargs)
    633 except Exception as e:
    634     et = datetime.utcnow()

Cell In[14], line 6, in func1()
      1 @logger.pretty_log(
      2     error_msg="OPS!",
      3 )
      4 def func1():
      5     time.sleep(1)
----> 6     raise ValueError("something wrong!")

ValueError: something wrong!

Nest Pretty Log Block.

You can nest one function in another too! It automatically gives you a human friendly visual effect.

[15]:
@logger.pretty_log()
def func2():
    time.sleep(1)
    logger.info("run func2")

@logger.pretty_log()
def func3():
    time.sleep(1)
    logger.info("run func3")
    with logger.nested():
        func2()

func3()
[User 2024-06-16 15:06:38] +----- Start func3() ----------------------------------------------------------+
[User 2024-06-16 15:06:38] |
[User 2024-06-16 15:06:39] | run func3
[User 2024-06-16 15:06:39] | +----- Start func2() --------------------------------------------------------+
[User 2024-06-16 15:06:39] | |
[User 2024-06-16 15:06:40] | | run func2
[User 2024-06-16 15:06:40] | |
[User 2024-06-16 15:06:40] | +----- End func2(), elapsed = 1.00 sec --------------------------------------+
[User 2024-06-16 15:06:40] |
[User 2024-06-16 15:06:40] +----- End func3(), elapsed = 2.02 sec ----------------------------------------+

You can customize the pipe character to create a semantic visual effect.

[16]:
@logger.pretty_log(pipe="🏭")
def run_build():
    time.sleep(1)
    logger.info("run build")

@logger.pretty_log(pipe="🧪")
def run_test():
    time.sleep(1)
    logger.info("run test")
    with logger.nested():
        run_build()

@logger.pretty_log(pipe="🚀")
def run_deploy():
    time.sleep(1)
    logger.info("run deploy")
    with logger.nested():
        run_test()

run_deploy()
[User 2024-06-16 15:06:40] +----- Start run_deploy() -----------------------------------------------------+
[User 2024-06-16 15:06:40] 🚀
[User 2024-06-16 15:06:41] 🚀 run deploy
[User 2024-06-16 15:06:41] 🚀 +----- Start run_test() -----------------------------------------------------+
[User 2024-06-16 15:06:41] 🚀 🧪
[User 2024-06-16 15:06:42] 🚀 🧪 run test
[User 2024-06-16 15:06:42] 🚀 🧪 +----- Start run_build() --------------------------------------------------+
[User 2024-06-16 15:06:42] 🚀 🧪 🏭
[User 2024-06-16 15:06:43] 🚀 🧪 🏭 run build
[User 2024-06-16 15:06:43] 🚀 🧪 🏭
[User 2024-06-16 15:06:43] 🚀 🧪 +----- End run_build(), elapsed = 1.00 sec --------------------------------+
[User 2024-06-16 15:06:43] 🚀 🧪
[User 2024-06-16 15:06:43] 🚀 +----- End run_test(), elapsed = 2.01 sec -----------------------------------+
[User 2024-06-16 15:06:43] 🚀
[User 2024-06-16 15:06:43] +----- End run_deploy(), elapsed = 3.02 sec -----------------------------------+

start_and_end Decorator

start_and_end decorator is just a wrapper of the pretty_log decorator, it provide nice looking emoji for start, end and error. start_and_end is basically a equavilent of:

@pretty_log(
    start_emoji="🕑 🟢 Start",
    error_emoji="⏰ 🔴 Error",
    end_emoji="⏰ 🔴 End",
)
def your_function():
    ...
[17]:
@logger.start_and_end(
    msg="My Function 1",
    start_emoji="🟢",
    end_emoji="🔴",
    pipe="📦",
)
def my_func1(name: str):
    time.sleep(1)
    logger.info(f"{name} do something in my func 1")

my_func1(name="alice")
[User 2024-06-16 15:06:43] +----- 🕑 🟢 Start 'My Function 1' ----------------------------------------------+
[User 2024-06-16 15:06:43] 📦
[User 2024-06-16 15:06:44] 📦 alice do something in my func 1
[User 2024-06-16 15:06:44] 📦
[User 2024-06-16 15:06:44] +----- ⏰ 🔴 End 'My Function 1', elapsed = 1.01 sec ----------------------------+

emoji_block Decorator

emoji_block decorator is just a wrapper of the start_and_end decorator.

  1. Use 🕑 {emoji} at begin.

  2. Use {emoji} at the end if exit successfully.

  3. Use {emoji} at the end if raises an error.

[18]:
@logger.emoji_block(
    msg="Deploy app {app_name}", # set a semantic title
    emoji="🚀",
)
def deploy_app(app_name: str):
    logger.info("working ...")
    logger.info("done")

deploy_app(app_name="my_app")
[User 2024-06-16 15:06:44] +----- 🕑 🚀 Start 'Deploy app my_app' ------------------------------------------+
[User 2024-06-16 15:06:44] 🚀
[User 2024-06-16 15:06:44] 🚀 working ...
[User 2024-06-16 15:06:44] 🚀 done
[User 2024-06-16 15:06:44] 🚀
[User 2024-06-16 15:06:44] +----- ⏰ ✅ 🚀 End 'Deploy app my_app', elapsed = 0.01 sec ----------------------+
[19]:
@logger.emoji_block(msg="build", emoji="🏭")
def run_build():
    time.sleep(1)
    logger.info("run build")

@logger.emoji_block(msg="test", emoji="🧪")
def run_test():
    time.sleep(1)
    logger.info("run test")
    with logger.nested():
        run_build()

@logger.emoji_block(msg="deploy", emoji="🚀")
def run_deploy():
    time.sleep(1)
    logger.info("run deploy")
    with logger.nested():
        run_test()

run_deploy()
[User 2024-06-16 15:06:44] +----- 🕑 🚀 Start 'deploy' -----------------------------------------------------+
[User 2024-06-16 15:06:44] 🚀
[User 2024-06-16 15:06:45] 🚀 run deploy
[User 2024-06-16 15:06:45] 🚀 +----- 🕑 🧪 Start 'test' -----------------------------------------------------+
[User 2024-06-16 15:06:45] 🚀 🧪
[User 2024-06-16 15:06:46] 🚀 🧪 run test
[User 2024-06-16 15:06:46] 🚀 🧪 +----- 🕑 🏭 Start 'build' --------------------------------------------------+
[User 2024-06-16 15:06:46] 🚀 🧪 🏭
[User 2024-06-16 15:06:47] 🚀 🧪 🏭 run build
[User 2024-06-16 15:06:47] 🚀 🧪 🏭
[User 2024-06-16 15:06:47] 🚀 🧪 +----- ⏰ ✅ 🏭 End 'build', elapsed = 1.01 sec ------------------------------+
[User 2024-06-16 15:06:47] 🚀 🧪
[User 2024-06-16 15:06:47] 🚀 +----- ⏰ ✅ 🧪 End 'test', elapsed = 2.02 sec ---------------------------------+
[User 2024-06-16 15:06:47] 🚀
[User 2024-06-16 15:06:47] +----- ⏰ ✅ 🚀 End 'deploy', elapsed = 3.03 sec ---------------------------------+

Integrate with Other Logging Library

You can use vislog library with any of your favorite logging library. You just need to set the logger as a custom logging.Logger() object or any object what support logger.debug("message here"), logger.info(...), logger.warning(...), logger.error(...), logger.critical(...). The visual logger will use this object to log the message. If not provided, a new logger will be created by default.

[20]:
# use loguru: https://github.com/Delgan/loguru
from loguru import logger as loguru_logger

logger = VisLog(logger=loguru_logger, name="vislog_demo")


@logger.emoji_block(msg="build", emoji="🏭")
def run_build():
    time.sleep(1)
    logger.info("run build")

@logger.emoji_block(msg="test", emoji="🧪")
def run_test():
    time.sleep(1)
    logger.info("run test")
    with logger.nested():
        run_build()

@logger.emoji_block(msg="deploy", emoji="🚀")
def run_deploy():
    time.sleep(1)
    logger.info("run deploy")
    with logger.nested():
        run_test()

run_deploy()
2024-06-16 15:57:47.639 | INFO     | vislog.impl:ruler:441 - +----- 🕑 🚀 Start 'deploy' -----------------------------------------------------+
2024-06-16 15:57:47.640 | INFO     | vislog.impl:_log:318 - 🚀 
2024-06-16 15:57:48.645 | INFO     | vislog.impl:_log:318 - 🚀 run deploy
2024-06-16 15:57:48.648 | INFO     | vislog.impl:ruler:441 - 🚀 +----- 🕑 🧪 Start 'test' -----------------------------------------------------+
2024-06-16 15:57:48.650 | INFO     | vislog.impl:_log:318 - 🚀 🧪 
2024-06-16 15:57:49.654 | INFO     | vislog.impl:_log:318 - 🚀 🧪 run test
2024-06-16 15:57:49.657 | INFO     | vislog.impl:ruler:441 - 🚀 🧪 +----- 🕑 🏭 Start 'build' --------------------------------------------------+
2024-06-16 15:57:49.659 | INFO     | vislog.impl:_log:318 - 🚀 🧪 🏭 
2024-06-16 15:57:50.663 | INFO     | vislog.impl:_log:318 - 🚀 🧪 🏭 run build
2024-06-16 15:57:50.666 | INFO     | vislog.impl:_log:318 - 🚀 🧪 🏭 
2024-06-16 15:57:50.668 | INFO     | vislog.impl:ruler:441 - 🚀 🧪 +----- ⏰ ✅ 🏭 End 'build', elapsed = 1.01 sec ------------------------------+
2024-06-16 15:57:50.669 | INFO     | vislog.impl:_log:318 - 🚀 🧪 
2024-06-16 15:57:50.670 | INFO     | vislog.impl:ruler:441 - 🚀 +----- ⏰ ✅ 🧪 End 'test', elapsed = 2.02 sec ---------------------------------+
2024-06-16 15:57:50.671 | INFO     | vislog.impl:_log:318 - 🚀 
2024-06-16 15:57:50.673 | INFO     | vislog.impl:ruler:441 - +----- ⏰ ✅ 🚀 End 'deploy', elapsed = 3.03 sec ---------------------------------+

More Public API