The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Devel::Optic - Production safe data inspector

VERSION

version 0.015

SYNOPSIS

use Devel::Optic;
my $optic = Devel::Optic->new();
my $foo = { bar => ['baz', 'blorg', { clang => 'pop' }] };

# 'HASH: {bar => ARRAY} (1 keys)"
$optic->inspect('$foo');

# 'ARRAY: [baz, blorg, HASH] (len 3)'
$optic->inspect(q|$foo->{'bar'}|);

# 'pop (len 3)'
$optic->inspect(q|$foo->{'bar'}->[-1]->{'clang'}|);

DESCRIPTION

Devel::Optic is a fiberscope for Perl programs. Just like a real fiberscope, it provides 'nondestructive inspection' of your variables. In other words: use this in your production environment to figure out what the heck is in your variables, without worrying whether the reporting code will blow up your program by trying shove gigabytes into the logging pipeline.

It provides a basic Perl-ish syntax (a 'query') for extracting bits of complex data structures from a Perl scope based on the variable name. This is intended for use by debuggers or similar introspection/observability tools where the consuming audience is a human troubleshooting a system.

Devel::Optic will summarize the selected data structure into a short, human-readable message. No attempt is made to make the summary contents machine-readable: it should be immediately passed to a logging pipeline or other debugging tool.

NAME

Devel::Optic - Production safe variable inspector

METHODS

new

my $o = Devel::Optic->new(%options);

%options may be empty, or contain any of the following keys:

uplevel

Which Perl scope to view. Default: 1 (scope that Devel::Optic is called from)

scalar_truncation_size

Size, in substr length terms, that scalar values are truncated to for viewing. Default: 256.

scalar_sample_size

Size, in substr length terms, that scalar children of a summarized data structure are trimmed to for inclusion in the summary. Default: 64.

sample_count

Number of keys/indices to display when summarizing a hash or arrayref. Default: 4.

inspect

my $stuff = { foo => ['a', 'b', 'c'] };
my $o = Devel::Optic->new;
# 'a (len 1)'
$o->inspect(q|$stuff->{'foo'}->[0]|);

This is the primary method. Given a query, it will return a summary of the data structure found at that path.

fit_to_view

my $some_variable = ['a', 'b', { foo => 'bar' }, [ 'blorg' ] ];

my $o = Devel::Optic->new();
# "ARRAY: [ 'a', 'b', HASH, ARRAY ]"
$o->fit_to_view($some_variable);

This method takes a Perl object/data structure and produces a 'squished' summary of that object/data structure. This summary makes no attempt to be comprehensive: its goal is to maximally aid human troubleshooting efforts, including efforts to refine a previous invocation of Devel::Optic with a more specific query.

full_picture

This method takes a 'query' and uses it to extract a data structure from the Devel::Optic's uplevel. If the query points to a variable that does not exist, Devel::Optic will croak.

QUERY SYNTAX

Devel::Optic uses a Perl-ish data access syntax for queries.

A query always starts with a variable name in the scope being picked, and uses -> to indicate deeper access to that variable. At each level, the value should be a key or index that can be used to navigate deeper or identify the target data.

For example, a query like this:

%my_cool_hash->{'a'}->[1]->{'needle'}

Applied to a scope like this:

my %my_cool_hash = (
    a => ["blub", { needle => "find me!", some_other_key => "blorb" }],
    b => "frobnicate"
);

Will return the value:

"find me!"

A less specific query on the same data structure:

%my_cool_hash->{'a'}

Will return that branch of the tree:

["blub", { needle => "find me!", some_other_key => "blorb" }]

Other syntactic examples:

$hash_ref->{'a'}->[0]->[3]->{'blorg'}
@array->[0]->{'foo'}
$array_ref->[0]->{'foo'}
$scalar

QUERY SYNTAX ALTNERATIVES

The query syntax attempts to provide a reasonable amount of power for navigating Perl data structures without risking the stability of the system under inspection.

In other words, while eval '$my_cool_hash{a}->[1]->{needle}' would be a much more powerful solution to the problem of navigating Perl data structures, it opens up all the cans of worms at once.

The current syntax might be a little bit "uncanny valley" in that it looks like Perl, but is not Perl. It is Perl-ish. It also might be too complex, since it allows fancy things like nested resolution:

$foo->{$bar}

Or even:

%my_hash->{$some_arrayref->[$some_scalar->{'key'}]}->{'needle'}

Ouch. In practice I hope and expect that the majority of queries will be simple scalars, or maybe one or two chained hashkey/array index lookups.

I'm open to exploring other syntax in this area as long as it is aligned with the following goals:

Simple query model

As a debugging tool, you have enough on your brain just debugging your system. Second-guessing your query syntax when you get unexpected results is a major distraction and leads to loss of trust in the tool (I'm looking at you, ElasticSearch).

O(1), not O(n) (or worse)

I'd like to avoid globs or matching syntax that might end up iterating over unbounded chunks of a data structure. Traversing a small, fixed number of keys in 'parallel' sounds like a sane extension, but anything which requires iterating over the entire set of hash keys or array indicies is likely to surprise when debugging systems with unexpectedly large data structures.

SEE ALSO

AUTHOR

Ben Tyler <btyler@cpan.org>

COPYRIGHT AND LICENSE

This software is copyright (c) 2019 by Ben Tyler.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.