Expand description

Functionality for use by backend plugins.

Backend plugins are executables that expose a gRPC server, which Grafana uses to communicate all information. This SDK uses tonic as its gRPC implementation.

The basic requirements for a backend plugin are to provide a main function which:

  • first, calls the initialize function from this module to initialize the plugin. This must be done before any other code can output to stdout or stderr!
  • performs any setup required by the plugin (such as connecting to databases or initializing any state)
  • creates the struct representing the plugin’s services, which should implement one of the various traits exposed by this module
  • create a Plugin to manage the plugin’s lifecycle with Grafana
  • use the Plugin::*_service methods to attach your plugin
  • begin serving on the listener returned by initialize using Plugin::start.

If you’re not sure where to start, take a look at the Plugin struct, its four important methods, and the trait bounds for each of them - those give an idea of what your plugin will need to implement.

Logging and tracing

The SDK provides a preconfigured tracing_subscriber::fmt::Layer which, when installed, will emit structured logs in a format understood by Grafana. Use the layer function to get the Layer, and install it using tracing_subscriber::Registry::with. Alternatively use Plugin::init_subscriber to automatically install a subscriber using the RUST_LOG environment variable to set the directive (defaulting to info if not set).

Once the layer is installed, any logs emitted by the various log or tracing macros will be emitted to Grafana.

Example

use futures_util::stream::FuturesOrdered;
use grafana_plugin_sdk::{backend, data, prelude::*};
use thiserror::Error;
use tonic::transport::Server;
use tracing::info;

#[derive(Clone, Debug)]
struct MyPlugin;

/// An error that may occur during a query.
///
/// This must store the `ref_id` of the query so that Grafana can line it up.
#[derive(Debug, Error)]
#[error("Error querying backend for query {ref_id}: {source}")]
struct QueryError {
    source: data::Error,
    ref_id: String,
}

impl backend::DataQueryError for QueryError {
    fn ref_id(self) -> String {
        self.ref_id
    }
}

#[tonic::async_trait]
impl backend::DataService for MyPlugin {

    /// The type of JSON data sent from Grafana to our backend plugin.
    ///
    /// This will correspond to the `TQuery` type parameter of the frontend
    /// datasource.
    ///
    /// We can use `serde_json::Value` if we want to accept any JSON.
    type Query = serde_json::Value;

    /// The type of error that could be returned by an individual query.
    type QueryError = QueryError;

    /// The type of iterator we're returning.
    ///
    /// In general the concrete type will be impossible to name in advance,
    /// so the `backend::BoxDataResponseStream` type alias will be useful.
    type Stream = backend::BoxDataResponseStream<Self::QueryError>;

    /// Respond to a request for data from Grafana.
    ///
    /// This request will contain zero or more queries, as well as information
    /// about the datasource instance on behalf of which this request is made,
    /// such as address, credentials, etc.
    ///
    /// Our plugin must respond to each query and return an iterator of `DataResponse`s,
    /// which themselves can contain zero or more `Frame`s.
    async fn query_data(&self, request: backend::QueryDataRequest<Self::Query>) -> Self::Stream {
        Box::pin(
            request
                .queries
                .into_iter()
                .map(|x| async {
                    // Here we create a single response Frame for each query.
                    // Frames can be created from iterators of fields using [`IntoFrame`].
                    Ok(backend::DataResponse::new(
                        x.ref_id.clone(),
                        // Return zero or more frames.
                        // A real implementation would fetch this data from a database
                        // or something.
                        vec![[
                            [1_u32, 2, 3].into_field("x"),
                            ["a", "b", "c"].into_field("y"),
                        ]
                        .into_frame("foo")
                        .check()
                        .map_err(|source| QueryError {
                            ref_id: x.ref_id,
                            source,
                        })?],
                    ))
                })
                .collect::<FuturesOrdered<_>>(),
        )
    }
}

#[grafana_plugin_sdk::main(services(data))]
async fn plugin() -> MyPlugin {
    // Create our plugin struct. Any state, such as a database connection, should be
    // held here, perhaps inside an `Arc` if required.
    MyPlugin
}

Structs

Enums

Traits

Functions

  • Initialize the plugin, returning the TcpListener that the gRPC service should serve on.
  • Create a tracing Layer configured to log events in a format understood by Grafana.

Type Aliases

Attribute Macros

  • Re-export of async_trait proc macro, so plugin implementations don’t have to import tonic manually.