Module grafana_plugin_sdk::backend
source · [−]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
usingPlugin::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
QueryDataRequest
.DataQuery
.Enums
Traits
DataService::query_data
.http::Response<Bytes>
.http::Response<Bytes>
.Functions
TcpListener
that the gRPC service should serve on.Type Definitions
DataService::query_data
.Attribute Macros
async_trait
proc macro, so plugin implementations don’t have to import tonic manually.