Skip to content
This repository was archived by the owner on Mar 3, 2022. It is now read-only.

Commit

Permalink
By brian.reese: Created new Logger module.
Browse files Browse the repository at this point in the history
  • Loading branch information
brianreese authored and TravisCarden committed May 6, 2015
1 parent 2767a71 commit d176436
Show file tree
Hide file tree
Showing 9 changed files with 402 additions and 0 deletions.
70 changes: 70 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Logger

## Contents of This File

- [Introduction](#introduction)
- [Installation](#installation)
- [Instrumentation](#instrumentation)


## Introduction

Current maintainer: [whitehouse](https://www.drupal.org/u/whitehouse)

The Logger module provides event logging facilities that are decoupled from any
particular storage backend. It is useful for module developers who want to
instrument their code for monitoring and alerting without forcing their users
to use a particular backend technology, like StatsD. The module doesn't provide
any visible functionality out-of-the-box. You only need it if you're a module
developer looking to instrument your code or if a module you're using depends on
it.


## Installation

Logger itself is installed in the usual way. See [Installing contributed
modules](https://www.drupal.org/documentation/install/modules-themes/modules-7).
An implementation of `hook_logger_event()` is required to connect to a logging
backend so as to actually do something with event data. See [logger.api.php]
(logger.api.php). This implementation may be supplied in custom code or by a
contributed module like [StatsD](https://www.drupal.org/project/statsd).


## Instrumentation

Instrumentation is the process of adding "probes" to your application code to
fire events for Logger to log. If you're installing Logger as a dependency of
another module, presumably that module is already instrumented. To instrument
your own code, simply invoke `logger_event()`, much as you would [`watchdog()`]
(https://api.drupal.org/api/drupal/includes!bootstrap.inc/function/watchdog/7).
See [logger.module](logger.module).

If you're instrumenting a contributed module, you must [declare a dependency on
Logger in your .info file](https://www.drupal.org/node/542202#dependencies).
Alternatively, you can create a "soft", or optional, dependency on Logger by
using a wrapping function instead of calling `logger_event()` directly, like
this:

```php
function example_logger_event($name, $type = 'count', $value = 1) {
if (function_exists('logger_event')) {
logger_event($name, $type, $value);
}
}
```

Event names should be [Graphite](http://graphite.readthedocs.org/)-compatible,
i.e., paths delimited by dots (`.`). See [Getting Your Data Into Graphite: Step 1]
(http://graphite.readthedocs.org/en/latest/feeding-carbon.html#step-1-plan-a-naming-hierarchy)
for some helpful advice.

Note: Logger has a debug mode that logs events to watchdog, which can be helpful
during development. It can be enabled at `admin/config/development/logging` or
by setting the `logger_debug` variable directly, e.g.:

```bash
# Enable debugging.
drush variable-set --exact logger_debug 1
# Disable debugging.
drush variable-delete --exact logger_debug
```
58 changes: 58 additions & 0 deletions logger.api.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

/**
* @file
* Hooks provided by the Logger module.
*/

/**
* @addtogroup hooks
* @{
*/

/**
* Log a system event.
*
* This hook allows modules to log events to custom facilities, such as StatsD.
*
* @param string $name
* The name of the event to log.
* @param string $type
* The type of metric to log--one of the following values corresponding to the
* @link https://github.com/etsy/statsd/blob/master/docs/metric_types.md StatsD Metric Types @endlink
* :
* - count: The corresponding value is a number by which to increment (or
* decrement, if negative) a simple counter.
* - gauge: The corresponding value is a single datum, which remains constant
* until explicitly changed.
* - set: The corresponding value is a value to add to a set of unique values.
* - time: The corresponding value is a duration in milliseconds.
* @param int $value
* The numeric value you wish to log.
*
* @see logger_event()
*/
function hook_logger_event($name, $type, $value) {
// Send the metric to StatsD.
switch ($type) {
case 'count':
StatsD::updateStats($name, $value);
break;

case 'gauge':
StatsD::gauge($name, $value);
break;

case 'time':
StatsD::timing($name, $value);
break;

case 'set':
// The StatsD module does not currently support the "set" type.
break;
}
}

/**
* @} End of "addtogroup hooks".
*/
27 changes: 27 additions & 0 deletions logger.form.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

/**
* @file
* Form functions for the Logger module.
*/

/**
* Implements hook_form_alter().
*/
function logger_form_alter(&$form, &$form_state, $form_id) {
// In order for hook_form_FORM_ID_alter() implementations to be detected in
// this file, an implementation of hook_form_alter() is needed, even if empty.
// @see https://api.drupal.org/comment/8714#comment-8714
}

/**
* Implements hook_form_FORM_ID_alter() for system_logging_settings().
*/
function logger_form_system_logging_settings_alter(&$form, &$form_state, $form_id) {
$form['logger_debug'] = array(
'#type' => 'checkbox',
'#title' => t('Enable Logger debugging'),
'#default_value' => variable_get('logger_debug', FALSE),
'#description' => t('Log Logger events to Watchdog for debugging. <strong>Warning:</strong> This can add significant load to the database and should not be enabled in production under normal circumstances.'),
);
}
6 changes: 6 additions & 0 deletions logger.info
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
name = Logger
description = Provides event logging facilities decoupled from storage backend.
core = 7.x
files[] = tests/logger.test
package = Developer
configure = admin/config/development/logging
17 changes: 17 additions & 0 deletions logger.install
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

/**
* @file
* Install, update and uninstall functions for the Logger module.
*/

/**
* Implements hook_uninstall().
*/
function logger_uninstall() {
// Delete persistent variables.
db_delete('variable')
->condition('name', db_like('logger_') . '%', 'LIKE')
->execute();
cache_clear_all('variables', 'cache_bootstrap');
}
69 changes: 69 additions & 0 deletions logger.module
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

/**
* @file
* Provides event logging facilities decoupled from storage backend.
*/

/**
* Implements hook_hook_info_alter().
*/
function logger_hook_info_alter(&$hooks) {
// Check HOOK.form.inc files for hook_form_alter() implementations.
$hooks['form_alter']['group'] = 'form';
}

/**
* Logs a system event.
*
* Note: Backends like @link http://graphite.readthedocs.org/ Graphite @endlink
* interpret dots (.) as path delimiters, like forward slashes (/) in a file
* path. You need to account for this if part of your event name is dynamically
* generated and may contain dots. For example:
*
* @code
* $ip_address = str_replace('.', '_', $_SERVER['REMOTE_ADDR']);
* logger_event(sprintf('server.remote_addr.%s', $ip_address);
* @endcode
*
* @param string $name
* The name of the event you want to log.
* @param string $type
* (optional) The type of metric to log--one of the following values
* corresponding to the
* @link https://github.com/etsy/statsd/blob/master/docs/metric_types.md StatsD Metric Types @endlink
* :
* - count: The corresponding value is a number by which to increment (or
* decrement, if negative) a simple counter.
* - gauge: The corresponding value is a single datum, which remains constant
* until explicitly changed.
* - set: The corresponding value is a value to add to a set of unique values.
* - time: The corresponding value is a duration in milliseconds.
* @param int $value
* (optional) The numeric value you wish to log. Defaults to 1.
*
* @see hook_logger_event()
*/
function logger_event($name, $type = 'count', $value = 1) {
// Assert valid event type.
$valid_types = array('count', 'gauge', 'set', 'time');
if (!in_array($type, $valid_types)) {
throw new InvalidArgumentException(sprintf('Invalid event type: "%s".', $type));
}

// Assert valid event value.
if (!is_int($value)) {
throw new InvalidArgumentException(sprintf('Invalid event value: "%s".', $value));
}

// Conditionally log events to watchdog for debugging purposes.
if (variable_get('logger_debug', FALSE)) {
watchdog('logger', 'Logger Event: @name | @type | @value', array(
'@name' => $name,
'@type' => $type,
'@value' => $value,
), WATCHDOG_DEBUG);
}

module_invoke_all('logger_event', $name, $type, $value);
}
134 changes: 134 additions & 0 deletions tests/logger.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<?php

/**
* @file
* Tests for Logger module.
*/

/**
* Unit tests for Logger.
*/
class LoggerUnitTestCase extends DrupalUnitTestCase {

/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'Logger unit tests',
'description' => 'Test Logger classes and functions.',
'group' => 'Logger',
);
}

/**
* Test valid arguments for logger_event().
*/
public function testValidLoggerEventFunction() {
$test_cases = array();
$test_cases[] = array(
'name' => 'test.valid_logger_event.name',
'type' => 'count',
'value' => 10,
);

foreach ($test_cases as $test_case) {
try {
logger_event($test_case['name'], $test_case['type'], $test_case['value']);
$this->pass('Passed with valid "type" and "value" parameters');
}
catch (InvalidArgumentException $e) {
$this->fail($e->getMessage());
}
}
}

/**
* INVALID arguments for logger_event() are expected to throw exceptions.
*/
public function testinvalidLoggerEventFunction() {
$test_cases = array();
$test_cases[] = array(
'name' => 'test.invalid_logger_event.name',
'type' => '',
'value' => '',
'success_message' => t('Correctly threw exception for empty "value" parameter.'),
'failure_message' => t('Incorrectly accepted an empty "value" parameter.'),
);
$test_cases[] = array(
'name' => 'test.invalid_logger_event.name',
'type' => 'invalidType',
'value' => 10,
'success_message' => t('Correctly threw exception for "type" parameter value not found in the $valid_types array.'),
'failure_message' => t('Incorrectly accepted a "type" parameter value not found in the $valid_types array.'),
);
$test_cases[] = array(
'name' => 'test.invalid_logger_event.name',
'type' => 'count',
'value' => 'invalidValue',
'success_message' => t('Correctly threw exception for a non-integer "value" parameter value.'),
'failure_message' => t('Incorrectly accepted a non-integer "value" parameter value.'),
);

foreach ($test_cases as $test_case) {
try {
logger_event($test_case['name'], $test_case['type'], $test_case['value']);
$this->fail($test_case['failure_message']);
}
catch (InvalidArgumentException $e) {
$this->pass($test_case['success_message'] . ' Exception messge: ' .
$e->getMessage());
}
}
}

}


/**
* Functional tests for Logger.
*/
class LoggerWebTestCase extends DrupalWebTestCase {
protected $privilegedUser;

/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'Logger functional tests',
'description' => 'Test the functionality of Logger.',
'group' => 'Logger',
);
}

/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp('logger_test');
$permissions = array('access content');
$this->privilegedUser = $this->drupalCreateUser($permissions);
$this->drupalLogin($this->privilegedUser);
}

/**
* Test hook_logger_event().
*
* @see logger_test_logger_event()
*/
public function testHookLoggerEvent() {
$expected_name = 'test.hook_logger_event.name';
$expected_type = 'count';
$expected_value = 100;
logger_event($expected_name, $expected_type, $expected_value);
$actual_name = variable_get('logger_test_name');
$actual_type = variable_get('logger_test_type');
$actual_value = variable_get('logger_test_value');

$this->assertEqual($actual_name, $expected_name, 'calling hook_logger_event with name ' . $expected_name);
$this->assertEqual($actual_type, $expected_type, 'calling hook_logger_event with type ' . $expected_type);
$this->assertEqual($actual_value, $expected_value, 'calling hook_logger_event with value ' . $expected_value);
}

}
6 changes: 6 additions & 0 deletions tests/modules/logger_test/logger_test.info
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
name = Logger tests
description = Support module for logger testing.
core = 7.x
dependencies[] = logger
package = Testing
hidden = TRUE
Loading

0 comments on commit d176436

Please sign in to comment.