Docs Pricing Blog

Sentry's 2nd Happy Hour in San Francisco

We had an awesome turnout at last month’s Happy Hour, thanks to everyone who made it! Our next Happy Hour will be next Tuesday, so come grab a drink on us and meet everyone in the community.

When

Tuesday, February 9th at 6PM.

Where

Willows, 1582 Folsom St.

How to write bulletproof function wrappers in JavaScript

image

In our client JavaScript SDK – Raven.js – we make use of a lot of function wrapping. We even provide a utility API method, Raven.wrap, that automatically wraps a function in try/catch and passes any caught errors to Raven.captureException, Raven’s primary error reporting function.

var wrappedFunc = Raven.wrap(function () {
  foo(); // ReferenceError: foo is not defined
});
wrappedFunc(); // catches error, reports to Sentry

Over the years I’ve written a lot of function wrappers. They’re handy, powerful tools that belong in the JavaScript programmer’s toolbox. And if there’s one thing I’ve learned, it’s that wrapping a function in JavaScript is trickier than it looks.

In this article, we’ll learn how function wrappers are useful, how they’re written, and how they should be written. But first …

Why use function wrappers?

A “function wrapper” is a function whose purpose is to call a second, “wrapped” function, with some minor amount of additional computation. Function wrapping is a common practice in JavaScript (and most other languages), with many practical uses:

Binding this

You’re probably familiar with Function.prototype.bind, which returns a function that, when called, has its this keyword set to the provided value:

function whosThis() {
  console.log(this);
}
whosThis.call('me'); // => 'me'

var boundWhosThis = whosThis.bind('them');

boundWhosThis(); // => 'them'
boundWhosThis.call('us'); // => 'them' (cannot override `this`)

You can think of bind as producing a function wrapper whose purpose is to call the original function, with a small additional quirk: it permanently changes the value of this.

We can do the same thing manually, without bind, but instead using a closure:

var them = 'them';
var boundWhosThis = function () {
  whosThis.call(them);
};
boundWhosThis(); // 'them'
boundWhosThis.call('us'); // => 'them' (cannot override `this`)

If you’ve spent a good amount of time writing browser-based JavaScript applications, you know how important it can be to manage the value of this across different functional scopes. Function wrappers produced by Function.prototype.bind are a handy, commonly-used tool.

Profiling

A function wrapper can be used to transparently record the duration of a function invocation. This can be helpful when profiling the performance of your application.

function profile(func, funcName) {
  return function () {
    var start = new Date(),
      returnVal = func.apply(this, arguments),
      end = new Date(),
      duration = stop.getTime() - start.getTime();

    console.log(`${funcName} took ${duration} ms to execute`);
    return returnVal;
  };
}

var profiledMax = profile(Math.max, 'Math.max');
profiledMax.call(Math, 1, 2);
// => "Math.max took 2 ms to execute"

Note that this example only times the duration of synchronous code. If the wrapped function triggers an asynchronous callback (e.g. via setTimeout), any time spent in that callback will not be captured.

Mixins

Mixin patterns in JavaScript are often used to augment a prototype method by wrapping it with additional behavior.

In the example below, the makeRoyalMixin function changes an object by wrapping that object’s getName prototype method with a function that changes its output:

function User(name) {
  this.name = name;
}

User.prototype.getName = function () {
  return this.name;
};

function makeRoyalMixin(klass) {
  var oldGetName = klass.prototype.getName;
  var designation = ordinal((Math.random() % 10) + 1); // '1st', '3rd', '9th', etc

  klass.prototype.getName = function () { // the wrapper
    return oldGetName.call(this) + ' the ' + designation;
  };
}

var user = new User('Janey Smith');
user.getName(); // => "Janey Smith'

makeRoyalMixin(User);
user.getName(); // => "Janey Smith the 7th"

But wait, there’s more

Profiling and Mixins are just two simple examples. Here’s a few other useful applications:

  • Code coverage – use function wrappers to detect if a function has been invoked during the execution of an application
  • Hiding complexity – use function wrappers to provide a simpler API than the underlying code
  • Cross-platform code – function wrappers are often used to smooth-out minor incompatibilities between an API on different platforms
  • Partial applications – use a function wrapper to fix specific arguments to that function’s invocation
  • Safely handling errors – our very first example, using function wrappers to transparently try/catch errors

Writing bulletproof function wrappers

Okay, we now know what function wrappers are, and how they’re commonly used. Now it’s time to write one. And not just any function wrapper – a wrapper that can wrap any conceivable function, invoked every which way, without breaking anything.

Use apply and arguments

Let’s say you want to wrap a known function, like XMLHttpRequest.prototype.open. You look up the signature of open on MDN, and learn that it has 5 arguments: method, url, async, user, and password. You write a wrapper for this function, passing the 5 declared arguments to the original function using call.

var origOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (method, url, async, user, password) {
  // rewrite URLs containing '/foo/' to '/bar/'
  url = url.replace(/\/foo\//, '/bar/')
  return origOpen.call(this, method, url, async, user, password);
};

But there’s a problem with this implementation. It’s subtle, but you have changed the behavior of calling open.

Remember that open takes an optional number of arguments. At minimum, it can accept just method and url. Or it can accept method, url, and async. Or it can accept all 5:

var xhr = new XMLHttpRequest();
xhr.open('GET', '/example');
// or
xhr.open('GET', '/example', false); // synchronous request
// or
xhr.open('GET', '/example', true, 'zerocool', 'hacktheplanet'); // async w/ HTTP auth

It turns out that in some JavaScript engines, the native open implementation actually inspects the number of arguments passed.

Consider this hypothetical native implementation:

// pretend native open implementation
XMLHttpRequest.prototype.open = function (method, url, async, user, password) {
  if (arguments.length <= 3) {
    this._simpleRequest(method, url, async);
  } else if (arguments.length === 5) {
    this._httpAuthRequest(method, url, async, user, password);
  }
}

In the function wrapper we wrote, we have changed the value of arguments.length as passed to the original open method. By doing .call(this, method, url, async, user, password), we have guaranteed that arguments.length will always be 5, regardless of how many arguments were passed to the wrapper.

In the hypothetical native implementation above, this means the _simpleRequest code path will never be reached; it always calls _httpAuthRequest, because arguments.length is always 5.

The solution: always pass an array of arguments to the wrapped function, matching the length of the arguments provided to the wrapper. If you need to edit one of the values, make a copy of the arguments object, edit the affected value, and pass the copy.

var origOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (method, url) {
  // copy arguments
  var args = [].slice.call(arguments, 0);

  // rewrite URLs containing '/foo/' to '/bar/'
  args[1] = url.replace(/\/foo\//, '/bar/')

  // arguments.length will always be same
  return origOpen.apply(this, args);
};

Note that in the example above, we’ve changed from declaring 5 parameters (method, url, async, user, password) to just 2 (method and url). Why?

Preserve arity

Like arrays and strings, function objects actually have a length property. This property reports the arity of a function – the number of declared parameters in its function signature.

For example, in the code sample below, foo function has an arity of 3, and bar has an arity of 0 (no formal parameters):

function foo (a, b, c) {
}
foo.length // => 3

function bar () {
}
bar.length // => 0

Back to XMLHttpRequest.prototype.open. Despite the documented number of variables being 5, the reported arity via the length property is actually 2:

origOpen.length // => 2 (the original open function)
XMLHttpRequest.prototype.open.length // => 2 (our wrapper)

You might be wondering – why does this matter? Well, just as we saw earlier that some code branches differently because of arguments.length, there is code out there that branches differently depending on the number of parameters declared in a function. If, in wrapping a function, we change its arity, we risk changing the behavior of code that inspects that function’s length property.

Mocha test functions and the impact of arity

Consider for a moment, Mocha, a popular JavaScript testing framework.

In Mocha, you declare a test function using the it function, which accepts both a descriptive string (what the test does) and the test function as arguments.

it('should work as expected', function () {
  assert.equals(1, 1);
});

Mocha also allows you to declare an asynchronous test function. In this version, the test function itself must declare a done parameter, which will be passed a callback function to be invoked when the test function has finished. If an exception was thrown during the execution of the test function, the done callback accepts an Error object as an argument.

it('should work as expected, asynchronously', function (done) {
  setTimeout(function () {
    try {
      assert.equals(1, 0);
    } catch (e) {
      return done(e); // pass assertion failure to `done`
    }
    done(); // for some reason 1 does not equal 0, call `done` - panic and freak out
  }, 1000);
});

Mocha is a pretty popular testing library, and it’s likely that many of you are familiar with its synchronous vs asynchronous test API. What you may not know, however, is that Mocha decides whether a test function is asynchronous by inspecting that test function’s length property.

Here’s the relevant bit from Mocha’s source code:

function Runnable(title, fn) {
  this.title = title;
  this.fn = fn;
  this.async = fn && fn.length; // <-- RIGHT HERE
  this.sync = !this.async;
  this._timeout = 2000;
  this._slow = 75;
  this._enableTimeouts = true;
  this.timedOut = false;
  this._trace = new Error('done() called multiple times');
  this._retries = -1;
  this._currentRetry = 0;
}

Let’s say that you wrap a test function that is passed to Mocha’s test runner via it. If in wrapping that test function, you simply pass arguments and don’t redeclare the done variable, you will reduce its arity to 0 and Mocha will not consider the test function to be asynchronous. This will cause you serious grief as you try to figure out why the heck your test function doesn’t work anymore.

The purpose of showing you this Mocha code is to demonstrate that, yes, there is code out there that inspects the length property of a function, and a failure to preserve arity when wrapping a function could result in broken code. So whenever you can, redeclare your wrapped variables and preserve arity.

Do I really need to do all this?

I’ll admit – a lot of the examples in this blog post are rare. It’s not often that you will encounter code that behaves differently depending on arguments.length or Function.length. When writing function wrappers that operate on your own, known functions, it is unlikely that you will encounter such behavior.

But, if you’re a library author, or writing 3rd-party scripts that operate in an unknown environment, or want to really futureproof your code – it couldn’t hurt to safeguard against problematic behavior by using the techniques above when writing function wrappers.

Hopefully, with the skills you’ve learned today, you’ll know when and where to practice safe function wrapping. Good luck.

Capturing Java Exceptions with Sentry

image

Getting started with exception reporting in Java can be an intimidating prospect. The large number of dependency managers, logging frameworks, configuration methods, and programming languages that run on the JVM can create a dizzying number of options for a programmer to choose between, so we’ve put this guide together to help you navigate the world of modern Java exception tracking with Sentry.

Choosing an Integration

The Sentry Java SDK (raven-java) is hosted at Maven Central, a repository that hosts many widely distributed Java packages (JARs) and works with many of the popular JVM dependency managers, including Maven. A search for Raven on Maven Central yields several results for different distributions of the project, and in this post we’ll help you figure out which is the right one to use with your application.

The major components of the SDK can be divided into two categories:

  • Logging integrations, commonly referred to as appenders, used to translate log records generated by your application via a logging framework into Sentry events. These integrations are packaged as raven-logback, raven-log4j, and raven-log4j2.
  • Raven, used by the logging integrations to send events to a Sentry server. This is packaged as raven and also includes the logging handler for java.util.logging.

For most applications, we recommend using one of the logging integrations to communicate with a Sentry server, and we’ll be mainly focusing on those in this post. For existing applications that are already using a logging framework, integrating with Sentry is often as simple as adding or changing a few lines in your logging configuration files. We recommend using the logging integrations over interfacing with Raven directly for several reasons:

  • The logging APIs are easy to understand. The patterns and vocabulary used by logging APIs are familiar to many developers, even across different platforms.
  • Producing messages via the logging frameworks reduce the amount of boilerplate code. For example, compare the logback appender event building method with a call to the logger.error method.
  • Using a logging framework provides greater control over reporting configuration. It is easy to replace reporting to Sentry in your development environment with console logging by using a different logging configuration file. Contrast this with having to wrap all calls in conditionals or mocking out the Raven interface with a fake instance.
  • Many other libraries also utilize logging frameworks. Using the logging framework enables collecting better data from other libraries in your application that also utilize the logging system, such as database drivers or a web framework.

The Java Logging Ecosystem

If you’re new to Java and have a background in a platform such as Python with a de facto logging solution, one of the first things that you will notice is the large number of logging frameworks that are available and remain used today. To understand why so many different frameworks exist, we’ll start with a quick history lesson.

image

Log4j was introduced in 1999 and was one of the first widely adopted logging frameworks. A few years later in 2002, Sun introduced an alternative framework, java.util.logging (commonly referred to by the acronym “JUL”), which is distributed with the Java platform. In reaction, the Apache Commons Logging project was created with the intention of providing a unified logging API that could be used to interact with whichever backing logging implementation that the user preferred, including Log4j and JUL (among others.) However, yet another alternative “unified” logging API emerged in 2005 from the author of Log4j — SLF4J, the “Simple Logging Facade for Java.” Later, the author of Log4j and SLF4J released logback, which provided a native implementation of the SLF4J interface. Around the same time, development also began on the (now stable) Log4j 2without the author of the first version. Simple, right?

There was a lot of code that was written at different points in time during this history and like so much software there was — and still is — lot of debates on which framework is the best choice. Since many of these projects are still in use today, Sentry provides integrations for several of the most widely adopted frameworks including JUL, logback, Log4j, and Log4j 2. Getting started is typically a matter of configuration, and the documentation for each integration contains instructions on how to include the integration as a dependency as well as a sample configuration to help you get started.

Choosing the Right Integration for Your Projects

When choosing which logging framework to use for your own projects, that decision is often mandated to you by your choice of framework. For projects that don’t depend on a framework, targeting the SLF4J interface is often recommended due to its ubiquity and flexibility. The backing logging implementation is largely a matter of developer preference, with logback and Log4j 2 being common choices.

Starting from Scratch

For example, it only takes a few moments to start reporting to Sentry with SLF4J and logback.

  1. Add raven-logback to your project dependencies. If you’re using Maven, dependencies are declared in pom.xml, and you can see an example in our logback example project. If you’re not using Maven, the project details on Maven Central have dependency information for a variety of different package managers.

     <dependency>
         <groupId>net.kencochrane.raven</groupId>
         <artifactId>raven-logback</artifactId>
         <version>6.0.0</version>
     </dependency>
    
  2. Add the Sentry appender to your logback.xml configuration file. You can set the Sentry DSN for your project via JDNI, as the SENTRY_DSN environment variable or system property, or as part of the appender configuration using the <dsn> attribute as in the example below.

     <appender name="Sentry" class="net.kencochrane.raven.logback.SentryAppender">
         <dsn>https://public-key:secret-key@app.getsentry.com/1</dsn>
     </appender>
    
     <root level="warn">
         <appender-ref ref="Sentry"/>
     </root>
    
  3. Add logging calls to your application. It is usually most effective to add logging.error calls as far towards the top of the stack in your application as possible — good examples are a main method in a console application, or request handler in a web application — to capture as many exceptions as possible. It can also be helpful to log errors as a warning or lower level in other try/catch blocks where checked exceptions are captured.

     public static void main(String[] args)
     {
         try {
             application.run()
         } catch (Exception e) {
             logger.error("Caught exception!", e);
         }
     }
    
  4. That’s it! Your project should now be reporting errors to Sentry. Tell your boss that you deserve a promotion.

We have several other example integrations in our raven-java-examples repository, and are adding more all the time.

Beyond Log Messages

The Sentry Java SDK also provides additional features that extend beyond simple log messages and tracebacks.

HTTP Request Details

When developing a web application, much of the state that is useful for identifying patterns among exception reports is contained within the attributes of the HTTP request, such as the logged in user account, request URL, headers, and query parameters. If your deployment environment utilizes Java Servlets, these attributes are included automatically on all log messages that occur during the context of a request.

Mapped Diagnostic Context

In addition to HTTP request attributes, it can often be helpful to add additional context to exception reports that are specific to your application. For example, an if you’re running an e-commerce application, it can be helpful to track what step of a checkout process a customer was in when an error occurred. For a consumer product, it can be helpful to track the subscription that the user is a member of so you can effectively triage issues by customer impact. For a stream processing system, it can be helpful to track the component in the processing pipeline where the error occurred.

The SLF4J API provides a feature called the Mapped Diagnostic Context, or MDC, for recording this data. This data is represented as tags or extra data in the Sentry UI. (This feature is available when using the Log4j, Log4j 2, and logback appenders.)

MDC.put("checkout-step", "submit");
try {
    // Any exceptions raised during order submission will include the
    // `checkout-step` tag.
    order.submit();
} finally {
    MDC.remove("checkout-step");
}

In addition to user supplied tags, a few additional tags are included in each event, including the logger, log level, and hostname.

Beyond Beyond Log Messages

Asynchronous Logging

A common concern with over-the-network logging is the performance hit incurred from communicating synchronously with a remote server. Asynchronous logging prevents network latency due to logging from directly impacting your application. The SDK design also enables asynchronous logging by frameworks that do not natively support asynchronous appenders. The ability to tune thread pool size, priority, and queue size also allows you to have fine-grained control over how much logging effects your application’s CPU, memory, and latency profile.

Not Just for Java

Although the project is named raven-java on GitHub, the Sentry Java SDK can be used by any JVM language that provides Java interoperability capabilities, including Clojure, Scala, and Groovy. In many cases, the typically awkwardly-invoked Java methods can be made more idiomatic by using language-specific wrappers such as clojure/tools.logging or scala-logging, two libraries for Clojure and Scala, respectively, that provide SLF4J-compatible functionality with a more natural API.

As an example, here is a snippet from our Clojure example project:

(ns raven-clojure-example.core
  (:require [clojure.tools.logging :as log]))

(defn -main [& args]
  (try
    (println (/ 1 0))
      (catch Exception e
        (log/error e "Caught exception!"))))

At times, it can be preferable to use Raven directly (rather than via a logging framework) to avoid adding additional dependencies. It can often be straightforard to construct and send messages using Java interoperability such as in this Scala example from Apache Chronos, a popular distributed job scheduler.

What’s next?

Have an idea for a feature, or interested in helping us improve the world of JVM error tracking? Contact us on Twitter at @getsentry, check out the raven-java repository, or better yet, take a look at our jobs page.

Introducing Saved Searches

If you’ve been staring at your stream this week you probably noticed something a little different. Our predefined filters have been replaced with a new dropdown. That dropdown represents our new Saved Searches feature. It’s been going on eight months since we built initial support the feature on our backend, and we figured it was probably time to expose them to the world.

Default Searches

We’ve enabled a few default searches for you, which we believe represent the best common workflows within Sentry:

New Today
Issues which were first seen within the last 24 hours.

Needs Triage
Issues which have yet to be assigned to an individual.

Unresolved Issues
Effectively the previous default view.

Assigned to Me
Issues which have been assigned to you.

My Bookmarks
Issues which you have bookmarked.

Customizing Defaults

By default the Unresolved Issues search is active, but admins can now change that via the Manage Saved Searches button. You’ll also be able to access that same page by hitting the Settings tab and selecting Saved Searches in the sidebar. Within there you will find two options: the team default, and your personal default.

In our case, we find views like the New Today and Untriaged to be a lot more useful for a default, which is why we also allow users to override their personal default. Today this is only offered to admins, but we’ll be exposing that functionality to members in the near future.

Creating a new saved search is also extremely easy. Once you’ve got your search query entered, click the Saved Search selector and hit Save Current Search. From there you’ll be prompted to give it a name, and you can optionally make it the new default.

Looking Ahead

We’ll be iterating on this concept as we get feedback, but we’re already loving it. The next step for us is exposing a more accessible UI for non-admins so they can easily manage their personal default. Internally we’re also discussing how we might approach having user-specific saved searches that aren’t shared with your team by default.

Let us know how you like the feature!

Welcome Theresa Vermeersch

We’re thrilled to have Theresa Vermeersch joining our team!

Theresa is a coffee connoisseur, book nerd, and art and design enthusiast. Outside of the office, she can be found enjoying all the hiking, skiing, and running trails that northern California has to offer, or perfecting her at-home pizza recipe.

Help us welcome Theresa by saying hello on Twitter!