{"id":13481614,"url":"https://github.com/SpinGo/op-rabbit","last_synced_at":"2025-03-27T12:31:09.250Z","repository":{"id":31830077,"uuid":"35396992","full_name":"SpinGo/op-rabbit","owner":"SpinGo","description":"The Opinionated RabbitMQ Library for Scala and Akka","archived":false,"fork":false,"pushed_at":"2021-10-28T21:40:36.000Z","size":654,"stargazers_count":232,"open_issues_count":40,"forks_count":73,"subscribers_count":13,"default_branch":"master","last_synced_at":"2024-10-30T15:50:55.882Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SpinGo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-05-11T02:09:44.000Z","updated_at":"2024-06-08T20:37:43.000Z","dependencies_parsed_at":"2022-09-04T09:22:25.861Z","dependency_job_id":null,"html_url":"https://github.com/SpinGo/op-rabbit","commit_stats":null,"previous_names":[],"tags_count":44,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SpinGo%2Fop-rabbit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SpinGo%2Fop-rabbit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SpinGo%2Fop-rabbit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SpinGo%2Fop-rabbit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SpinGo","download_url":"https://codeload.github.com/SpinGo/op-rabbit/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245844869,"owners_count":20681790,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T17:00:53.380Z","updated_at":"2025-03-27T12:31:08.934Z","avatar_url":"https://github.com/SpinGo.png","language":"Scala","readme":"# Op-Rabbit\n\n##### An opinionated RabbitMQ library for Scala and Akka.\n\n# Documentation\n\nBrowse the latest [API Docs](https://op-rabbit.github.io/docs/index.html) online.\n\nIssues go here; questions can be posed there as well. Please see the\n[Cookbook](https://github.com/SpinGo/op-rabbit/wiki/Cookbook), first.\n\n- https://github.com/SpinGo/op-rabbit/issues\n\n# Intro\n\nOp-Rabbit is a high-level, type-safe, opinionated, composable,\nfault-tolerant library for interacting with RabbitMQ; the following is\na high-level feature list:\n\n- Recovery:\n    - Consumers automatically reconnect and subscribe if the\n      connection is lost\n    - Messages published will wait for a connection to be available\n- Integration\n    - Connection settings pulled from Typesafe config library\n    - Asynchronous, concurrent consumption using Scala native Futures\n      or the new Akka Streams project.\n    - Common pattern for serialization allows easy integration with\n      libraries such as play-json, json4s or upickle.\n    - Common pattern for exception handling to publish errors to\n      Airbrake, Syslog, or all of the above\n- Modular\n    - Composition favored over inheritance enabling flexible and high\n      code reuse.\n- Modeled\n    - Queue binding, exchange binding modeled with case classes\n    - Queue, and Exchange arguments, such as `x-ttl`, are modeled\n    - HeaderValues are modeled; if you try and provide RabbitMQ an\n      invalid type for a header value, the compiler will let you know.\n    - Publishing mechanisms also modeled\n- Reliability\n    - Builds on the excellent\n      [Akka RabbitMQ client](https://github.com/thenewmotion/akka-rabbitmq)\n      library for easy recovery.\n    - Built-in consumer error recovery strategy in which messages are\n      re-delivered to the message queue and retried (not implemented\n      for akka-streams integration as retry mechanism affects message\n      order)\n    - With a single message, pause all consumers if service health\n      check fails (IE: database unavailable); easily resume the same.\n- Graceful shutdown\n    - Consumers and streams can immediately unsubscribe, but stay\n      alive long enough to wait for any messages to finish being\n      processed.\n- Program at multiple levels of abstraction\n    - If op-rabbit doesn't do what you need it to, you can either\n      extend op-rabbit or interact directly with `akka-rabbitmq`\n      [Akka RabbitMQ client](https://github.com/thenewmotion/akka-rabbitmq).\n- Tested\n    - Extensive integration tests\n\n## Installation\n\nOp-Rabbit is available on Maven Central\n\n```scala\nval opRabbitVersion = \"2.1.0\"\n\nlibraryDependencies ++= Seq(\n  \"com.spingo\" %% \"op-rabbit-core\"        % opRabbitVersion,\n  \"com.spingo\" %% \"op-rabbit-play-json\"   % opRabbitVersion,\n  \"com.spingo\" %% \"op-rabbit-json4s\"      % opRabbitVersion,\n  \"com.spingo\" %% \"op-rabbit-upickle\"     % opRabbitVersion,\n  \"com.spingo\" %% \"op-rabbit-circe\"       % opRabbitVersion,\n  \"com.spingo\" %% \"op-rabbit-airbrake\"    % opRabbitVersion,\n  \"com.spingo\" %% \"op-rabbit-akka-stream\" % opRabbitVersion\n)\n```\n\n### Scala Version Compatibility Matrix:\n\n#### op-rabbit 2.1.x\n\nSupports Scala 2.12 and Scala 2.11.\n\n| module                       | dependsOn                | version   |\n| ---------------------------- | ------------------------ | --------- |\n| op-rabbit-core               | akka                     | 2.5.x     |\n|                              | akka-rabbitmq            | 5.1.x     |\n|                              | shapeless                | 2.3.x     |\n|                              | type-safe config         | 1.3.x     |\n| op-rabbit-play-json          | play-json                | 2.6.x     |\n| op-rabbit-json4s             | json4s                   | 3.5.x     |\n| op-rabbit-upickle            | upickle                  | 0.8.x     |\n| op-rabbit-circe              | circe                    | 0.9.x     |\n| op-rabbit-airbrake           | airbrake                 | 2.2.x     |\n| op-rabbit-akka-stream        | acked-stream             | 2.1.x     |\n\n#### op-rabbit 2.0.x\n\nSupports Scala 2.12 and Scala 2.11.\n\n| module                       | dependsOn                | version   |\n| ---------------------------- | ------------------------ | --------- |\n| op-rabbit-core               | akka                     | ~\u003e 2.4.17 |\n|                              | akka-rabbitmq            | 4.0       |\n|                              | shapeless                | ~\u003e 2.3.2  |\n|                              | type-safe config         | \u003e= 1.3.0  |\n| op-rabbit-play-json          | play-json                | 2.6.0-M5  |\n| op-rabbit-json4s             | json4s                   | 3.5.x     |\n| op-rabbit-circe              | circe                    | 0.7.x     |\n| op-rabbit-airbrake           | airbrake                 | 2.2.x     |\n| op-rabbit-akka-stream        | acked-stream             | 2.1.x     |\n\n#### op-rabbit 1.6.x\n\nSupports Scala 2.11 only\n\n| module                       | dependsOn                | version  |\n| ---------------------------- | ------------------------ | -------- |\n| op-rabbit-core               | akka                     | ~\u003e 2.4.2 |\n|                              | akka-rabbitmq            | 2.3      |\n|                              | shapeless                | 2.3.x    |\n|                              | type-safe config         | \u003e= 1.3.0 |\n| op-rabbit-play-json          | play-json                | 2.5.x    |\n| op-rabbit-json4s             | json4s                   | 3.4.x    |\n| op-rabbit-circe              | circe                    | 0.5.x    |\n| op-rabbit-airbrake           | airbrake                 | 2.2.x    |\n| op-rabbit-akka-stream        | acked-stream             | 2.1.x    |\n\n## A high-level overview of the available components:\n\n- `op-rabbit-core` [API](http://spingo-oss.s3.amazonaws.com/docs/op-rabbit/core/current/index.html)\n    - Implements basic patterns for serialization and message\n      processing.\n- `op-rabbit-play-json` [API](https://op-rabbit.github.io/docs/index.html#com.spingo.op_rabbit.PlayJsonSupport$)\n    - Easily use\n      [Play Json](https://www.playframework.com/documentation/2.4.x/ScalaJson)\n      formats to publish or consume messages; automatically sets\n      RabbitMQ message headers to indicate content type.\n- `op-rabbit-json4s` [API](https://op-rabbit.github.io/docs/index.html#com.spingo.op_rabbit.Json4sSupport$)\n    - Easily use [Json4s](http://json4s.org) to serialization\n      messages; automatically sets RabbitMQ message headers to\n      indicate content type.\n- `op-rabbit-upickle` [API](https://op-rabbit.github.io/docs/index.html#com.spingo.op_rabbit.upickleSupport$)\n    - Easily use [upickle](http://www.lihaoyi.com/upickle) message serialization as JSON or MessagePack;\n      automatically sets RabbitMQ message headers to indicate content type.\n- `op-rabbit-airbrake` [API](https://op-rabbit.github.io/docs/index.html#com.spingo.op_rabbit.AirbrakeLogger)\n    - Report consumer exceptions to airbrake, using the\n      [Airbrake](https://github.com/airbrake/airbrake-java) Java\n      library.\n- `op-rabbit-akka-stream` [API](https://op-rabbit.github.io/docs/index.html#com.spingo.op_rabbit.stream.package)\n    - Process or publish messages using akka-stream.\n\n## Upgrade Guide\n\nRefer to\n[Upgrade Guide wiki page](https://github.com/SpinGo/op-rabbit/wiki/Upgrading)\nfor help upgrading.\n\n## Usage\n\nSet up RabbitMQ connection information in `application.conf`:\n\n```conf\nop-rabbit {\n  topic-exchange-name = \"amq.topic\"\n  channel-dispatcher = \"op-rabbit.default-channel-dispatcher\"\n  default-channel-dispatcher {\n    # Dispatcher is the name of the event-based dispatcher\n    type = Dispatcher\n\n    # What kind of ExecutionService to use\n    executor = \"fork-join-executor\"\n\n    # Configuration for the fork join pool\n    fork-join-executor {\n      # Min number of threads to cap factor-based parallelism number to\n      parallelism-min = 2\n\n      # Parallelism (threads) ... ceil(available processors * factor)\n      parallelism-factor = 2.0\n\n      # Max number of threads to cap factor-based parallelism number to\n      parallelism-max = 4\n    }\n    # Throughput defines the maximum number of messages to be\n    # processed per actor before the thread jumps to the next actor.\n    # Set to 1 for as fair as possible.\n    throughput = 100\n  }\n  connection {\n    virtual-host = \"/\"\n    hosts = [\"127.0.0.1\"]\n    username = \"guest\"\n    password = \"guest\"\n    port = 5672\n    ssl = false\n    connection-timeout = 3s\n  }\n}\n```\n\nNote that hosts is an array; Connection attempts will be made to hosts\nin that order, with a default timeout of `3s`. This way you can\nspecify addresses of your rabbitMQ cluster, and if one of the\ninstances goes down, your application will automatically reconnect to\nanother member of the cluster.\n\n`topic-exchange-name` is the default topic exchange to use; this can\nbe overriden by passing `exchange = \"my-topic\"` to\n[TopicBinding](http://spingo-oss.s3.amazonaws.com/docs/op-rabbit/core/current/index.html#com.spingo.op_rabbit.TopicBinding)\nor\n[Message.topic](http://spingo-oss.s3.amazonaws.com/docs/op-rabbit/core/current/index.html#com.spingo.op_rabbit.Message$).\n\nBoot up the RabbitMQ control actor:\n\n```scala\nimport com.spingo.op_rabbit.RabbitControl\nimport akka.actor.{ActorSystem, Props}\n\nimplicit val actorSystem = ActorSystem(\"such-system\")\nval rabbitControl = actorSystem.actorOf(Props[RabbitControl])\n```\n\n### Set up a consumer: (Topic subscription)\n\n(this example uses `op-rabbit-play-json`)\n\n```scala\nimport com.spingo.op_rabbit.PlayJsonSupport._\nimport com.spingo.op_rabbit._\nimport play.api.libs.json._\n\nimport scala.concurrent.ExecutionContext.Implicits.global\ncase class Person(name: String, age: Int)\n// setup play-json serializer\nimplicit val personFormat = Json.format[Person]\nimplicit val recoveryStrategy = RecoveryStrategy.none\n\nval subscriptionRef = Subscription.run(rabbitControl) {\n  import Directives._\n  // A qos of 3 will cause up to 3 concurrent messages to be processed at any given time.\n  channel(qos = 3) {\n    consume(topic(queue(\"such-message-queue\"), List(\"some-topic.#\"))) {\n      (body(as[Person]) \u0026 routingKey) { (person, key) =\u003e\n        /* do work; this body is executed in a separate thread, as\n           provided by the implicit execution context */\n        println(s\"\"\"A person named '${person.name}' with age\n          ${person.age} was received over '${key}'.\"\"\")\n        ack\n      }\n    }\n  }\n}\n```\n\nNow, test the consumer by sending a message:\n\n```\nsubscriptionRef.initialized.foreach { _ =\u003e\n  rabbitControl ! Message.topic(\n    Person(\"Your name here\", 33), \"some-topic.cool\")\n}\n```\n\nStop the consumer:\n\n```\nsubscriptionRef.close()\n```\n\nNote, if your call generates an additional future, you can pass it to\nack, and message will be acked based off the Future success, and\nnacked with Failure (such that the configured\n[RecoveryStrategy](http://spingo-oss.s3.amazonaws.com/docs/op-rabbit/core/current/index.html#com.spingo.op_rabbit.RecoveryStrategy)\nif the Future fails):\n\n```scala\n  // ...\n      (body(as[Person]) \u0026 routingKey) { (person, key) =\u003e\n        /* do work; this body is executed in a separate thread, as\n           provided by the implicit execution context */\n        val result: Future[Unit] = myApi.methodCall(person)\n        ack(result)\n      }\n  // ...\n\n```\n#### Consuming from existing queues\nIf the queue already exists and doesn't match the expected configuration, topic subscription will fail. To bind to an externally configured queue use `Queue.passive`:\n\n```scala\n  channel(qos = 3) {\n    consume(Queue.passive(\"very-exist-queue\")) { ...\n```\n\nIt is also possible to optionally create the queue if it doesn't exist, by providing a `QueueDefinition` instead of a `String`:\n\n```scala\n  channel(qos = 3) {\n    consume(Queue.passive(topic(queue(\"wow-maybe-queue\"), List(\"some-topic.#\")))) { ...\n```\n#### Accessing additional headers\n\nAs seen in the example above, you can extract headers in addition to\nthe message body, using op-rabbit's\n[Directives](http://spingo-oss.s3.amazonaws.com/docs/op-rabbit/core/current/index.html#com.spingo.op_rabbit.Directives). You\ncan use multiple declaratives via multiple nested functions, as\nfollows:\n\n```scala\nimport com.spingo.op_rabbit.properties._\n\n// Nested directives\n// ...\n      body(as[Person]) { person =\u003e\n        optionalProperty(ReplyTo) { replyTo =\u003e\n          // do work\n          ack\n        }\n      }\n// ...\n```\n\nOr, you can combine directives using `\u0026` to form a compound directive, as follows:\n\n```scala\n// Compound directive\n// ...\n      (body(as[Person]) \u0026 optionalProperty(ReplyTo)) { (person, replyTo) =\u003e\n        // do work\n        ack\n      }\n// ...\n```\n\nSee the documentation on [Directives](http://spingo-oss.s3.amazonaws.com/docs/op-rabbit/core/current/index.html#com.spingo.op_rabbit.Directives) for more details.\n\n#### Shutting down a consumer\n\nThe following methods are available on a [SubscriptionRef](http://spingo-oss.s3.amazonaws.com/docs/op-rabbit/core/current/index.html#com.spingo.op_rabbit.SubscriptionRef) which will allow control over the subscription.\n\n```scala\n/* stop receiving new messages from RabbitMQ immediately; shut down\n   consumer and channel as soon as pending messages are completed. A\n   grace period of 30 seconds is given, after which the subscription\n   forcefully shuts down. (Default of 5 minutes used if duration not\n   provided) */\nsubscription.close(30 seconds)\n\n/* Shut down the subscription immediately; don't wait for messages to\n   finish processing. */\nsubscription.abort()\n\n/* Future[Unit] which completes once the provided binding has been\n   applied (IE: queue has been created and topic bindings\n   configured). Useful if you need to assert you don't send a message\n   before a message queue is created in which to place it. */\nsubscription.initialized\n\n// Future[Unit] which completes when the subscription is closed.\nsubscription.closed\n```\n\n#### Recovery strategy:\n\nA recovery strategy defines how a subscription should handle exceptions and **must be provided**. Should it redeliver\nthem a limited number of times? Or, should it drop them? Several pre-defined recovery strategies with their\ncorresponding documentation are defined in the\n[RecoveryStrategy](http://spingo-oss.s3.amazonaws.com/docs/op-rabbit/current/index.html#com.spingo.op_rabbit.RecoveryStrategy$)\ncompanion object.\n\n```\nimplicit val recoveryStrategy = RecoveryStrategy.nack()\n```\n\n### Publish a message:\n\n```scala\nrabbitControl ! Message.topic(\n  Person(name = \"Mike How\", age = 33),\n  routingKey = \"some-topic.very-interest\")\n\nrabbitControl ! Message.queue(\n  Person(name = \"Ivanah Tinkle\", age = 25),\n  queue = \"such-message-queue\")\n```\n\nBy default:\n\n- Messages will be queued up until a connection is available\n- Messages are monitored via publisherConfirms; if a connection is\n  lost before RabbitMQ confirms receipt of the message, then the\n  message is published again. This means that the message may be\n  delivered twice, the default opinion being that `at-least-once` is\n  better than `at-most-once`. You can use\n  [UnconfirmedMessage](http://spingo-oss.s3.amazonaws.com/docs/op-rabbit/core/current/index.html#com.spingo.op_rabbit.UnconfirmedMessage)\n  if you'd like `at-most-once` delivery, instead.\n- If you would like to be notified of confirmation, use the\n  [ask](http://doc.akka.io/docs/akka/2.3.12/scala/actors.html#Send_messages)\n  pattern:\n\n  ```scala\n  import akka.pattern.ask\n  import akka.util.Timeout\n  import scala.concurrent.duration._\n  implicit val timeout = Timeout(5 seconds)\n  val received = (\n    rabbitControl ? Message.queue(\n      Person(name = \"Ivanah Tinkle\", age = 25),\n      queue = \"such-message-queue\")\n  ).mapTo[ConfirmResponse]\n  ```\n\n### Consuming using Akka streams\n\n(this example uses `op-rabbit-play-json` and `op-rabbit-akka-streams`)\n\n```scala\n\nimport Directives._\nimplicit val recoveryStrategy = RecoveryStrategy.drop()\nRabbitSource(\n  rabbitControl,\n  channel(qos = 3),\n  consume(queue(\n    \"such-queue\",\n    durable = true,\n    exclusive = false,\n    autoDelete = false)),\n  body(as[Person])). // marshalling is automatically hooked up using implicits\n  runForeach { person =\u003e\n    greet(person)\n  } // after each successful iteration the message is acknowledged.\n```\n\nNote: `RabbitSource` yields an\n[AckedSource](https://github.com/timcharper/acked-stream/blob/master/src/main/scala/com/timcharper/acked/AckedSource.scala),\nwhich can be combined with an\n[AckedFlow](https://github.com/timcharper/acked-stream/blob/master/src/main/scala/com/timcharper/acked/AckedFlowOps.scala#L519)\nand an\n[AckedSink](https://github.com/timcharper/acked-stream/blob/master/src/main/scala/com/timcharper/acked/AckedSink.scala)\n(such as\n[`MessagePublisherSink`](http://spingo-oss.s3.amazonaws.com/docs/op-rabbit/akka-stream/current/index.html#com.spingo.op_rabbit.stream.MessagePublisherSink$)). You\ncan convert an acked stream into a normal stream by calling\n`AckedStream.acked`; once messages flow passed the `acked` component,\nthey are considered acknowledged, and acknowledgement tracking is no\nlonger a concern (and thus, you are free to use the akka-stream\nlibrary in its entirety).\n\n#### Stream failures and recovery strategies\n\nWhen using the DSL as described in the [consumer setup](https://github.com/SpinGo/op-rabbit#set-up-a-consumer-topic-subscription)\nsection, recovery strategies are triggered if [`fail`](https://github.com/SpinGo/op-rabbit/blob/a5e534a8d3e9b1a89544501acd334b983ecdb5c4/core/src/main/scala/com/spingo/op_rabbit/Directives.scala#L156)\nis called or if a failed future is passed to `ack`. For streams, we have to do\nsomething a little different.\n\nTo trigger the specified recovery strategy when using `op-rabbit-akka-stream`\nand its `acked` components, an exception should be thrown within the `acked`\npart of the graph. However, the default exception-handling behavior in\n`akka-stream` is stopping the graph, which in `op-rabbit`'s case would mean\nstopping the consumer and preventing further messages from being processed.\nTo explicitly allow the graph to continue running, a ResumingDecider supervision\nstrategy should be declared. (To learn more about supervision strategies please\nrefer to the [Akka Streams docs](https://doc.akka.io/docs/akka/current/stream/stream-error.html#supervision-strategies)).\n\n```scala\n  implicit val system = ActorSystem()\n  private val rabbitControl = system.actorOf(Props[RabbitControl], name = \"op-rabbit\")\n  // We define an ActorMaterializer with a resumingDecider supervision strategy,\n  // which prevents the graph from stopping when an exception is thrown.\n  implicit val materializer = ActorMaterializer(\n    ActorMaterializerSettings(system)\n      .withSupervisionStrategy(Supervision.resumingDecider: Decider)\n  )\n  // As a recovery strategy, let's suppose we want all nacked messages to go to\n  // an existing queue called \"failed-events\"\n  implicit private val recoveryStrategy = RecoveryStrategy.abandonedQueue(\n    7.days,\n    abandonQueueName = (_: String) =\u003e \"failed-events\"\n  )\n\n  private val src = RabbitSource(\n    rabbitControl,\n    channel(qos = 3),\n    consume(Queue(\"events\")),\n    body(as[String])\n  )\n\n  // This may throw an exception, in which case the defined recovery strategy\n  // will be triggered and our flow will continue thanks to the resumingDecider.\n  private val flow = AckedFlow[String].map(_.toInt)\n\n  private val sink = AckedSink.foreach[Int](println)\n  \n  src.via(flow).to(sink).run\n```\n\n### Publishing using Akka streams\n\n(this example uses `op-rabbit-play-json` and `op-rabbit-akka-streams`)\n\n```scala\nimport com.spingo.op_rabbit._\nimport com.spingo.op_rabbit.stream._\nimport com.spingo.op_rabbit.PlayJsonSupport._\nimplicit val workFormat = Format[Work] // setup play-json serializer\n\n/* Each element in source will be acknowledged after publish\n   confirmation is received */\nAckedSource(1 to 15).\n  map(Message.queue(_, queueName)).\n  to(MessagePublisherSink(rabbitControl))\n  .run\n```\n\nIf you can see the pattern here, combining an akka-stream rabbitmq\nconsumer and publisher allows for guaranteed at-least-once message\ndelivery from head to tail; in other words, don't acknowledge the\noriginal message from the message queue until any and all side-effect\nevents have been published to other queues and persisted.\n\n### Error notification\n\nIt's important to know when your consumers fail. Out of the box,\n`op-rabbit` ships with support for logging to `slf4j` (and therefore\nsyslog), and also `airbrake` via `op-rabbit-airbrake`. Without any\nadditional signal provided by you, slf4j will be used, making error\nvisibility a default.\n\nYou can report errors to multiple sources by combining error logging\nstrategies; for example, if you'd like to report to both `slf4j` and\nto `airbrake`, import / set the following implicit RabbitErrorLogging\nin the scope where your consumer is instantiated:\n\n```scala\nimport com.spingo.op_rabbit.{Slf4jLogger, AirbrakeLogger}\n\nimplicit val rabbitErrorLogging = Slf4jLogger + AirbrakeLogger.fromConfig\n```\n\nImplementing your own error reporting strategy is simple; here's the source code for the slf4jLogger:\n\n```scala\nobject Slf4jLogger extends RabbitErrorLogging {\n  def apply(\n    name: String,\n    message: String,\n    exception: Throwable,\n    consumerTag: String,\n    envelope: Envelope,\n    properties: BasicProperties,\n    body: Array[Byte]): Unit = {\n\n    val logger = LoggerFactory.getLogger(name)\n    logger.error(s\"${message}. Body=${bodyAsString(body, properties)}. Envelope=${envelope}\", exception)\n  }\n}\n```\n\n## Notes\n\n### Shapeless dependency\n\nNote, Op-Rabbit depends on\n[shapeless](https://github.com/milessabin/shapeless) `2.3.0`, and there is presently no published version of `spray-routing-shapeless2` which works with shapeless `2.3.0`. Consider migrating to `akka-http`, or if you must stay on spray, use [op-rabbit 1.2.x](https://github.com/SpinGo/op-rabbit/tree/v1.2.x), instead.\n\n## Credits\n\nOp-Rabbit was created by [Tim Harper](http://timcharper.com)\n\nThis library builds upon the excellent\n[Akka RabbitMQ client](https://github.com/thenewmotion/akka-rabbitmq)\nby Yaroslav Klymko.\n","funding_links":[],"categories":["Table of Contents","Messaging"],"sub_categories":["Messaging"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FSpinGo%2Fop-rabbit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FSpinGo%2Fop-rabbit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FSpinGo%2Fop-rabbit/lists"}