In the previous tutorial, you learned that every printk() message has a log level from 0 to 7. But knowing the log levels is only half the picture. The other half is understanding how to control which messages appear on your console — especially when you are actively developing and debugging a kernel module.
This tutorial covers everything you need to know about the console log level: how to read it, how to change it temporarily during development, how to make it persistent, and how to use dmesg effectively to filter kernel messages. It also covers the special pr_debug() behavior and how to unlock debug messages using dynamic debug.
The Three Places Where printk Messages Can Go
Before diving into how to control log levels, it helps to understand where kernel messages actually land. There are three destinations:
dmesg. Fixed size — old messages get overwritten when full.rsyslog or syslog-ng daemon reads from the kernel and writes to /var/log/kern.log or /var/log/syslog.Understanding this architecture is important: even if a message does not appear on your console, it is almost certainly in the ring buffer. If you cannot see a message on screen, your first action should always be to check dmesg.
How to Change the Console Log Level at Runtime
The most common need during kernel module development is to raise the console log level so that all pr_info() and pr_warn() messages appear directly on the terminal without needing dmesg. Here are the methods, from most convenient to most permanent:
Method 1: Write directly to /proc/sys/kernel/printk
This is the most direct method. You write four space-separated values representing the four log level parameters. The change takes effect immediately but is lost after a reboot.
# Show current values
cat /proc/sys/kernel/printk
# Set console log level to 8 — all printk messages appear on console
# Format: current_level default_level min_level boot_default_level
echo "8 4 1 7" | sudo tee /proc/sys/kernel/printk
# Verify the change
cat /proc/sys/kernel/printk
# Output: 8 4 1 7
# Restore to the typical production default
echo "4 4 1 7" | sudo tee /proc/sys/kernel/printk
Method 2: Use sysctl
The sysctl command provides a cleaner interface to the same kernel parameter. It reads from and writes to /proc/sys/ under the hood.
# Read the current value
sysctl kernel.printk
# Set all four values: console default min boot_default
sudo sysctl -w kernel.printk="8 4 1 7"
# Read back to verify
sysctl kernel.printk
# Output: kernel.printk = 8 4 1 7
Method 3: Make it persistent across reboots via /etc/sysctl.conf
If you want a custom log level to survive reboots — common on a dedicated development board — add it to the sysctl configuration file.
# Open /etc/sysctl.conf or create a drop-in file
sudo nano /etc/sysctl.d/99-kernel-debug.conf
# Add this line:
kernel.printk = 8 4 1 7
# Apply immediately without rebooting:
sudo sysctl -p /etc/sysctl.d/99-kernel-debug.conf
Method 4: Use the kernel boot parameter
For embedded Linux systems or systems where you want to set the log level very early in the boot process (before the filesystem mounts), you can set it as a kernel command-line parameter.
# Add to the kernel command line in your bootloader (GRUB, U-Boot, etc.)
# This sets console log level to 8 from the very beginning of boot
loglevel=8
# Example GRUB entry in /etc/default/grub:
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash loglevel=8"
# After editing, regenerate GRUB config:
sudo update-grub
Comparison: Which Method Should You Use?
| Method | Survives Reboot? | Takes Effect | Best For |
|---|---|---|---|
echo to /proc/sys/kernel/printk |
No | Instantly | Quick one-time change during a debug session |
sysctl -w kernel.printk |
No | Instantly | Same as above, cleaner syntax |
| /etc/sysctl.d/ config file | Yes | On next boot or after sysctl -p |
Development board or VM that you always use for kernel work |
Kernel boot parameter loglevel= |
Yes (in bootloader) | From earliest boot | Embedded targets, capturing early boot messages |
A Practical Pattern: Startup Script for Development Boards
When you regularly develop kernel modules on a dedicated board (a Raspberry Pi, BeagleBone, or any ARM SBC), it is convenient to set the console log level automatically when you log in as root. A lightweight way to do this is via a startup script or your shell’s profile.
#!/bin/sh
# /etc/profile.d/kernel-dev-setup.sh
# Automatically configure kernel log level for development
# Only applies when running as root
if [ "$(id -u)" -eq 0 ]; then
# Set console log level to 8: all printk messages go to console
echo "8 4 1 7" > /proc/sys/kernel/printk
echo "[kernel-dev] Console log level set to 8. All printk visible."
fi
id -u? The check $(id -u) -eq 0 makes sure this only runs when you are root. Writing to /proc/sys/kernel/printk requires root privileges. If a non-root user sources the script, the echo command would fail silently without this guard.
Filtering dmesg Output by Log Level
Even when messages are not appearing on your console, dmesg shows everything in the ring buffer. Modern versions of dmesg (from util-linux 2.21 onwards, which covers most Linux distributions using kernel 4.x and above) support powerful filtering options.
# Show messages from the current boot session only
dmesg
# Follow kernel messages in real time (like tail -f for kernel log)
dmesg -w
# Show messages with human-readable timestamps instead of seconds since boot
dmesg -T
# Filter by log level — useful to see only critical problems
dmesg --level=err
dmesg --level=crit,alert,emerg
dmesg --level=warn,err,crit,alert,emerg
# Show the last 30 lines
dmesg | tail -30
# Search for your module's messages by filtering on your module name or prefix
dmesg | grep "EP Demo"
# Clear the ring buffer before loading your module for a clean slate
sudo dmesg -C
sudo insmod my_module.ko
dmesg
sudo dmesg -C to clear the ring buffer. Then load with sudo insmod and immediately run dmesg. You will see only your module’s messages with no noise from previous activity.
Unlocking pr_debug() with Dynamic Debug
As covered in the previous tutorial, pr_debug() is compiled out unless the DEBUG macro is defined. However, the Linux kernel offers a more powerful and flexible system called Dynamic Debug (CONFIG_DYNAMIC_DEBUG), which lets you enable and disable debug messages at runtime without recompiling the kernel or your module.
Step 1: Check if Dynamic Debug is available
# Check if the dynamic debug control file exists
ls /sys/kernel/debug/dynamic_debug/control
# If the path does not exist, mount debugfs first:
sudo mount -t debugfs debugfs /sys/kernel/debug
Step 2: Enable pr_debug() for a specific module
# Enable all debug messages from a specific module
# Replace 'my_module' with your module's name
echo "module my_module +p" | sudo tee /sys/kernel/debug/dynamic_debug/control
# Enable debug messages from a specific source file only
echo "file my_module.c +p" | sudo tee /sys/kernel/debug/dynamic_debug/control
# Enable debug messages from a specific function only
echo "func my_init_function +p" | sudo tee /sys/kernel/debug/dynamic_debug/control
# Disable debug messages again
echo "module my_module -p" | sudo tee /sys/kernel/debug/dynamic_debug/control
Step 3: Alternative — Define DEBUG in your Makefile
If your kernel does not have CONFIG_DYNAMIC_DEBUG enabled, you can define the DEBUG macro specifically for your module in your Makefile. This causes pr_debug() and pr_devel() to be compiled in for that module only, without affecting the rest of the kernel.
# In your module's Makefile, add this line:
CFLAGS_my_module.o := -DDEBUG
# Full Makefile example:
obj-m += my_module.o
CFLAGS_my_module.o := -DDEBUG
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
Putting It All Together: The Complete Log Level Decision Flow
Message stored in memory. Readable via
dmesg at any time.
→ Message goes to console immediately
→ NOT shown on console. Use dmesg.
rsyslog or syslog-ng picks up the message and writes it to disk for persistent logging.
Common Mistakes When Working With Kernel Log Levels
On a default Linux system, the console log level is 4. Messages at level 6 (KERN_INFO) will not appear on the console. They are in the ring buffer — use
dmesg to see them, or raise the console level to 7 or 8.
Actually,
pr_debug() is a compile-time no-op unless DEBUG is defined. No matter how high you set the console log level, you will not see its output unless you enable CONFIG_DYNAMIC_DEBUG or rebuild with -DDEBUG.
Calling
pr_emerg() just to make a message appear on the console is wrong. It will appear on every terminal and in every log, and it trains operators to ignore genuine emergency messages. Use pr_info() and raise the console log level instead.
If your module produces many messages and the system has been running for a long time, early messages may have been overwritten. If you need long-term kernel logging, configure
rsyslog to capture and store kernel messages to disk.
Embedded Linux targets often have a default console log level of 3, not 4. This means even KERN_ERR messages do not appear on the serial console by default. Always read
/proc/sys/kernel/printk on a new target before concluding that your module’s messages are missing.
Interview Questions — Console Log Level Control
echo "8 4 1 7" | sudo tee /proc/sys/kernel/printk or sudo sysctl -w kernel.printk="8 4 1 7". Since all log levels are 0 through 7, every message has a level less than 8 and will appear on the console. This change is temporary and resets on reboot.CONFIG_DYNAMIC_DEBUG) that maintains a database of all pr_debug() and dev_dbg() call sites in the kernel and all loaded modules. You can enable or disable specific debug messages at runtime via /sys/kernel/debug/dynamic_debug/control — by module, by file, or by function — without recompiling. You use it when you want to trace a specific driver’s behavior in a production kernel without a full rebuild.pr_debug() is conditionally compiled. Unless the module was compiled with -DDEBUG in its CFLAGS, or the kernel was built with CONFIG_DYNAMIC_DEBUG enabled and the debug messages are activated at runtime, the pr_debug() calls are replaced with empty code that produces no output. The console log level is irrelevant for messages that were never compiled in./etc/sysctl.d/99-kernel-debug.conf with the line kernel.printk = 8 4 1 7. Run sudo sysctl -p /etc/sysctl.d/99-kernel-debug.conf to apply it immediately. On the next reboot, the system will read this file and apply the setting automatically. Alternatively, add loglevel=8 to the kernel command line in the bootloader configuration.dmesg -w (or dmesg --follow) waits for new kernel messages and prints them as they arrive, much like tail -f for a log file. This is available in util-linux 2.21 and later, which is standard on any modern Linux distribution. It is very useful for monitoring module loading and device events in real time.Continue Your Free Linux Kernel Programming Journey
EmbeddedPathashala offers 100% free courses on Linux Kernel Programming, Linux Device Drivers, Bluetooth/BLE, and Embedded Systems.
Visit EmbeddedPathashala