Skip to main content
Ctrl+K
PetaPerl - Home PetaPerl - Home
  • Introduction
  • Getting Started
  • CLI options
  • How-To
  • Reference · P5
  • Reference · PP
  • Introduction
  • Getting Started
  • CLI options
  • How-To
  • Reference · P5
  • Reference · PP

Section Navigation

  • PPerl Architecture
    • Runtimes
    • JIT Compilation
    • Auto-FFI (Peta::FFI)
    • Native Modules
    • Differences from Perl 5
  • Concurrent Execution
    • Parallel Execution
    • Threads
      • ithreads basics
      • Shared data
      • Alternatives to ithreads
  • Regular expressions
    • Basics
    • Character classes
    • Anchors and assertions
    • Quantifiers
    • Groups and captures
    • Alternation
    • Modifiers
    • Substitution
    • Unicode
    • Performance
  • Object-oriented programming
    • Modern classes
    • Classical OO
    • Inheritance and method resolution
    • Roles and delegation
    • Migrating from classical to modern
  • Debugging Perl programs
    • Preflight: catching bugs before runtime
    • Print debugging, warn, and the Carp family
    • Exceptions, die, and typed error objects
    • The interactive debugger
    • Breakpoints, actions, and watches
    • Inspecting state
    • Tracing: watching execution flow
    • IDE and DAP integration
  • The Definitive Perl One-Liner Guide
    • The switches
    • Progressive recipes
    • Regex-driven one-liners
    • Numeric and date one-liners
    • From one-liner to shell alias
    • Traps and surprises
  • pack and unpack — a tutorial
    • Bytes and widths
    • Endianness
    • Strings, bits, and nybbles
    • Grouping and counts
    • Positioning and padding
    • Network protocols — a DNS query
    • File formats — a GIF header
  • Opening files
    • Reading, writing, appending
    • Encoding and layers
    • Pipes
    • In-memory handles
    • Handling errors
  • References — a tutorial
    • Basics — taking references and using them
    • Arrays of arrays
    • Hashes and mixed structures
    • Anonymous references — [...] and {...}
    • Subroutine references
    • Weak references
  • Unicode — a tutorial
    • Strings and encodings
    • I/O
    • Regex and properties
    • Pitfalls
  • Boolean Logic for Perl Programmers — a tutorial
    • Truthiness
    • Operators
    • Truth tables
    • De Morgan’s laws
    • Functional completeness
    • Applications
  • How-To
  • Debugging Perl programs
  • Print debugging, warn, and the Carp family

Print debugging, warn, and the Carp family#

This chapter covers non-interactive diagnostics: inserted prints, variable dumps, stack traces from inside the program. These techniques remain the fastest path to understanding “what does my code actually see here?” for bugs that are cheap to reproduce.

Readers who already know about print and warn will find here the structured variants that earn their keep: the Carp caller-reporting family, Data::Dumper and its modern replacements, and the stack-trace-everywhere one-liners.

warn vs print — pick the right stream#

warn writes to STDERR; print defaults to STDOUT. Use warn for diagnostics: it keeps program output clean, survives STDOUT redirection, and is the stream loggers already capture.

warn "*** foo=[$foo] bar=[@bar]\n";

Wrap values in literal brackets so trailing whitespace and newlines are visible in the output. "foo=[$foo]" reveals foo=[  ] where "foo=$foo" just shows foo= .

Without a trailing \n, warn (and die) append at FILE line N. from the caller’s perspective. With \n, they do not. Rule of thumb: append \n for user-facing messages; omit it for developer diagnostics where you want location.

Cheap location tags from the compiler:

warn "reached $0 " . __FILE__ . ':' . __LINE__ . "\n";
warn "in @{[__SUB__->name]}\n";          # 5.16+

The Carp family — report from the caller#

When a library function fails, pointing at the library’s own source is almost never useful. Carp’s family reports from the caller’s frame:

Function

Reports from

Fatal?

Stack trace?

carp

caller

no

no

croak

caller

yes

no

cluck

caller

no

full

confess

caller

yes

full

use Carp;

sub open_config {
    my $path = shift // croak "open_config: missing path";
    open my $fh, '<', $path or croak "cannot read $path: $!";
    return $fh;
}

Library code should use croak/carp, not die/warn — the error message points at the caller’s line, which is where the fix goes.

Stack traces without touching the code#

Carp::Always installs $SIG{__DIE__} and $SIG{__WARN__} to route through Carp::confess / Carp::cluck. Run a misbehaving script with:

perl -MCarp::Always script.pl

Every die and warn produces a full stack. No source changes. This is the first thing to reach for when a crash gives a useless one-line message.

Devel::Confess is a drop-in equivalent with fewer edge cases around overloaded objects:

perl -d:Confess script.pl

Both modules add cost to every warn and die; use them while investigating, not in steady production.

For a permanent in-code install:

use Carp;
$SIG{__DIE__}  = sub { return if $^S; Carp::confess(@_) };
$SIG{__WARN__} = sub { Carp::cluck(@_) };

return if $^S; — “return if we are inside eval”. Without this guard every caught exception pays the trace cost. See exceptions for the full $SIG{__DIE__} pattern.

Dumping data structures#

Pass a reference to every dumper; dumping a bare hash or array prints its flat-list form and loses the structure.

Data::Dumper — universal, core, eval-roundtrippable#

use Data::Dumper;

$Data::Dumper::Sortkeys = 1;
$Data::Dumper::Indent   = 1;
$Data::Dumper::Terse    = 1;
$Data::Dumper::Deepcopy = 1;

warn Dumper(\%config);
  • Sortkeys makes output diff-stable.

  • Indent=1 is the readable setting (0 = flat, 2 = default, 3 = with array indices).

  • Terse=1 suppresses $VAR1 = prefixes when you do not need round-trip-ability.

  • Deepcopy=1 disables the “seen this ref before” $VAR1->{...} tracking — prints the data, not a graph reconstruction.

Data::Dumper::Concise is a drop-in with sensible defaults pre-applied; the DBIx::Class / Moose community standard.

Data::Printer — coloured, modern, readable#

use DDP;
p %config;                    # not a reference — DDP handles both

Data::Printer prints better-looking output than Data::Dumper, colours by type, handles objects specially (class, attributes, overloads), and does not try to round-trip. Prefer it when the goal is reading the dump, not re-ingesting it.

Data::Dump — compact one-liner#

use Data::Dump qw(dd);
dd \%config;                  # prints to STDERR in compact form

Gisle Aas’s Data::Dump. Useful when the default Data::Dumper output is too verbose for a single warn line.

When to reach for which#

  • Data::Dumper when you might feed the output back into eval or compare two dumps.

  • Data::Printer (DDP) when a human is reading the output.

  • Data::Dump when you want a one-line summary.

  • Data::Dumper::Concise on projects already using it.

Conditional debug prints#

A compile-time constant is optimised out entirely when false:

use constant DEBUG => 0;

warn "state=$state\n" if DEBUG;

With DEBUG => 0, the if DEBUG test is folded at compile time and the warn never appears in the op tree — zero runtime cost.

Wire to the environment so the flag is flippable without editing:

use constant DEBUG => $ENV{APP_DEBUG} // 0;

Multiple channels via bitmask:

use constant { WEB => 1, SQL => 2, REGEX => 4 };
my $DEBUG = $ENV{APP_DEBUG} // 0;

sub dbg { my $c = shift; warn "[DBG] @_\n" if $DEBUG & $c }

dbg WEB,   "request $req_id start\n";
dbg SQL,   "query: $sql\n";
dbg REGEX, "matched: $&\n";         # (unless you care about perf — see below)

Source-filter alternative: Smart::Comments#

use Smart::Comments;

my @stuff = compute();
### @stuff                 # prints: @stuff: [...]
### Checking key: $key     # print, plus asserts $key is truthy
### Processing |=| ... for @stuff

Lines beginning ### are rewritten into prints (and, with special syntax, progress bars and assertions). The module is a source filter — it rewrites the program text before compile — so reported line numbers can drift by one. When not imported, the ### comments compile to nothing. The drift is the price of not cluttering the code with explicit warn lines.

Taint and $& — incidental costs to know about#

Referencing $&, $`, or $' anywhere in the program forces perl to maintain these match-context globals for every regex match in the entire program, including matches inside loaded modules. Historically this imposed a noticeable slowdown; on modern perls the cost is smaller but non-zero, and the rule still applies: do not touch them if you can avoid it.

Use @- / @+ (match position arrays), named captures, or the /p modifier with ${^MATCH}, ${^PREMATCH}, ${^POSTMATCH} instead. The /p variants impose the cost only for matches that opt in.

Timestamped logging#

Hand-rolled warn with localtime is adequate for one-off investigations:

use Time::HiRes qw(gettimeofday);
sub t { my ($s, $us) = gettimeofday; sprintf "%d.%06d", $s, $us }
warn sprintf "[%s] state=%s\n", t(), $state;

For anything more structured, use a logging framework rather than rolling your own:

  • Log::Any — library-safe; emit logs without picking a backend. Pair with an adapter (Log::Any::Adapter::Log4perl, ::Stdout, ::Syslog) in the application.

  • Log::Log4perl — category / level / appender / layout; config file-driven.

  • Log::Dispatch — lower-level router; often used under Log::Any.

Standard levels, lowest to highest: TRACE, DEBUG, INFO, WARN, ERROR, FATAL. Gate TRACE / DEBUG behind an env flag in production.

Find out more#

  • exceptions — die, $@, $SIG{__DIE__}, typed exception classes, the modern try/catch.

  • interactive-debugger — when inserting prints costs more than launching perl -d.

  • tracing — full-program line-by-line trace without editing the source.

previous

Preflight: catching bugs before runtime

next

Exceptions, die, and typed error objects

On this page
  • warn vs print — pick the right stream
  • The Carp family — report from the caller
  • Stack traces without touching the code
  • Dumping data structures
    • Data::Dumper — universal, core, eval-roundtrippable
    • Data::Printer — coloured, modern, readable
    • Data::Dump — compact one-liner
    • When to reach for which
  • Conditional debug prints
  • Source-filter alternative: Smart::Comments
  • Taint and $& — incidental costs to know about
  • Timestamped logging
  • Find out more
Source (accessible & AI-friendly)

© 2025-present PetaMem, s.r.o.  ·  rendered —

Documentation under development!

Disclaimer & bug reports  ·  License  ·  PDF  ·  Download pperl