├── .gitignore ├── README.md ├── Vagrantfile ├── agent ├── libnewrelic-collector-client.so ├── libnewrelic-common.so ├── libnewrelic-transaction.so └── newrelic ├── c_src ├── Makefile ├── lib │ ├── libnewrelic-collector-client.so │ ├── libnewrelic-common.so │ └── libnewrelic-transaction.so ├── main.cc ├── main.h ├── newrelic_collector_client.h ├── newrelic_common.h ├── newrelic_transaction.h ├── operation_msg.pb.cc ├── operation_msg.pb.h └── operation_msg.proto ├── config ├── config.exs └── log4cplus.properties ├── lib ├── newrelic_elixir.ex └── newrelic_elixir │ ├── api │ ├── agent.ex │ ├── segment.ex │ └── transaction.ex │ ├── comm.ex │ └── messages.ex ├── mix.exs ├── mix.lock └── test ├── newrelic_elixir_test.exs └── test_helper.exs /.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /cover 3 | /deps 4 | erl_crash.dump 5 | *.ez 6 | .vagrant 7 | /c_src/newrelic 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ElixirNewrelic 2 | 3 | New Relic Elixir Agent. This uses the official New Relic Agent SDK. 4 | 5 | ## Current Status 6 | The New Relic agent SDK can only be built on linux currently. 7 | This is a pre-release, only a simple transaction and error tracing is currently available. 8 | 9 | ## Getting started 10 | 11 | ### Add the Cure dependency to your mix.exs file: 12 | ```elixir 13 | def deps do 14 | [{:elixir_newrelic, "~> 0.2.1"}] 15 | end 16 | ``` 17 | ### Fetch & compile dependencies 18 | ```sh 19 | mix deps.get 20 | mix deps.compile 21 | ``` 22 | 23 | ### download the application or build from source 24 | 25 | In the releases there is a 'newrelic' application or you can compile it from source 26 | 27 | #### compiling from source 28 | 29 | git clone this repository. 30 | ```sh 31 | mix deps.get 32 | mix deps.compile 33 | mix compile 34 | mic compile.cure 35 | ``` 36 | 37 | This will also run the Makefile and create the newrelic application file 38 | 39 | ### install dependencies 40 | 41 | 1. Install libcurl 42 | 2. Install openssl 43 | 3. Install protobuf (used for Elixir to C++ communication) 44 | 4. Copy the New Relic shared object to /usr/local/lib (can also be found in the release) 45 | 46 | ### configure the agent 47 | 48 | ```elixir 49 | config :elixir_newrelic, 50 | newrelic_location: "/absolute/path/to/newrelic/application", 51 | license: "my_newrelic_license_key", 52 | app_name: "my_appname_in_newrelic" 53 | ``` 54 | 55 | ### In Elixir 56 | 57 | ```elixir 58 | {:ok, server} = ElixirNewrelic.start_link 59 | {:ok, init} = ElixirNewrelic.Api.Agent.init(server) 60 | {:ok, response} = ElixirNewrelic.Api.Transaction.transaction_begin(server, "test") 61 | exception_type = "Error" 62 | error_message = "Something went wrong" 63 | stack_trace = "..." 64 | stack_frame_delimiter = "\n" 65 | transaction_id = response.transaction_id 66 | {:ok, response} = ElixirNewrelic.Api.Transaction.transaction_notice_error(server, transaction_id, exception_type, error_message, stack_trace, stack_frame_delimiter) 67 | {:ok, response} = ElixirNewrelic.Api.Transaction.transaction_end(server, transaction_id) 68 | ``` 69 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | # All Vagrant configuration is done below. The "2" in Vagrant.configure 5 | # configures the configuration version (we support older styles for 6 | # backwards compatibility). Please don't change it unless you know what 7 | # you're doing. 8 | Vagrant.configure(2) do |config| 9 | # The most common configuration options are documented and commented below. 10 | # For a complete reference, please see the online documentation at 11 | # https://docs.vagrantup.com. 12 | 13 | # Every Vagrant development environment requires a box. You can search for 14 | # boxes at https://atlas.hashicorp.com/search. 15 | config.vm.box = "hashicorp/precise64" 16 | 17 | # Disable automatic box update checking. If you disable this, then 18 | # boxes will only be checked for updates when the user runs 19 | # `vagrant box outdated`. This is not recommended. 20 | # config.vm.box_check_update = false 21 | 22 | # Create a forwarded port mapping which allows access to a specific port 23 | # within the machine from a port on the host machine. In the example below, 24 | # accessing "localhost:8080" will access port 80 on the guest machine. 25 | # config.vm.network "forwarded_port", guest: 80, host: 8080 26 | 27 | # Create a private network, which allows host-only access to the machine 28 | # using a specific IP. 29 | # config.vm.network "private_network", ip: "192.168.33.10" 30 | 31 | # Create a public network, which generally matched to bridged network. 32 | # Bridged networks make the machine appear as another physical device on 33 | # your network. 34 | # config.vm.network "public_network" 35 | 36 | # Share an additional folder to the guest VM. The first argument is 37 | # the path on the host to the actual folder. The second argument is 38 | # the path on the guest to mount the folder. And the optional third 39 | # argument is a set of non-required options. 40 | # config.vm.synced_folder "../data", "/vagrant_data" 41 | 42 | # Provider-specific configuration so you can fine-tune various 43 | # backing providers for Vagrant. These expose provider-specific options. 44 | # Example for VirtualBox: 45 | # 46 | # config.vm.provider "virtualbox" do |vb| 47 | # # Display the VirtualBox GUI when booting the machine 48 | # vb.gui = true 49 | # 50 | # # Customize the amount of memory on the VM: 51 | # vb.memory = "1024" 52 | # end 53 | # 54 | # View the documentation for the provider you are using for more 55 | # information on available options. 56 | 57 | # Define a Vagrant Push strategy for pushing to Atlas. Other push strategies 58 | # such as FTP and Heroku are also available. See the documentation at 59 | # https://docs.vagrantup.com/v2/push/atlas.html for more information. 60 | # config.push.define "atlas" do |push| 61 | # push.app = "YOUR_ATLAS_USERNAME/YOUR_APPLICATION_NAME" 62 | # end 63 | 64 | # Enable provisioning with a shell script. Additional provisioners such as 65 | # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the 66 | # documentation for more information about their specific syntax and use. 67 | # config.vm.provision "shell", inline: <<-SHELL 68 | # sudo apt-get update 69 | # sudo apt-get install -y apache2 70 | # SHELL 71 | end 72 | -------------------------------------------------------------------------------- /agent/libnewrelic-collector-client.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joeyfeldberg/elixir-newrelic/07a102ac3d899e356c7ca827975d27c60ebf6a25/agent/libnewrelic-collector-client.so -------------------------------------------------------------------------------- /agent/libnewrelic-common.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joeyfeldberg/elixir-newrelic/07a102ac3d899e356c7ca827975d27c60ebf6a25/agent/libnewrelic-common.so -------------------------------------------------------------------------------- /agent/libnewrelic-transaction.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joeyfeldberg/elixir-newrelic/07a102ac3d899e356c7ca827975d27c60ebf6a25/agent/libnewrelic-transaction.so -------------------------------------------------------------------------------- /agent/newrelic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joeyfeldberg/elixir-newrelic/07a102ac3d899e356c7ca827975d27c60ebf6a25/agent/newrelic -------------------------------------------------------------------------------- /c_src/Makefile: -------------------------------------------------------------------------------- 1 | INSTALL_PATH?=/usr/local/lib/ 2 | INC_PARAMS = $(PWD)/deps/cure/c_src 3 | ELIXIR_COMM_C = $(INC_PARAMS)/elixir_comm.c 4 | LIB_DIR = $(PWD)/c_src/lib/ 5 | C_FLAGS = -I$(INC_PARAMS) -O3 -fPIC 6 | LDFLAGS = -L$(LIB_DIR) -lnewrelic-transaction -lnewrelic-common -lnewrelic-collector-client -lprotobuf 7 | INSTALL_FILES = `find $(LIB_DIR) -type f 2>/dev/null` 8 | 9 | ALL = clean newrelic 10 | 11 | # Targets: 12 | all: $(ALL) 13 | clean: 14 | rm -f $(PWD)/c_src/newrelic 15 | newrelic: 16 | gcc -o newrelic main.cc operation_msg.pb.cc $(ELIXIR_COMM_C) $(C_FLAGS) $(LDFLAGS) 17 | install: 18 | install $(LIB_DIR)/*.so $(INSTALL_PATH) 19 | -------------------------------------------------------------------------------- /c_src/lib/libnewrelic-collector-client.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joeyfeldberg/elixir-newrelic/07a102ac3d899e356c7ca827975d27c60ebf6a25/c_src/lib/libnewrelic-collector-client.so -------------------------------------------------------------------------------- /c_src/lib/libnewrelic-common.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joeyfeldberg/elixir-newrelic/07a102ac3d899e356c7ca827975d27c60ebf6a25/c_src/lib/libnewrelic-common.so -------------------------------------------------------------------------------- /c_src/lib/libnewrelic-transaction.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joeyfeldberg/elixir-newrelic/07a102ac3d899e356c7ca827975d27c60ebf6a25/c_src/lib/libnewrelic-transaction.so -------------------------------------------------------------------------------- /c_src/main.cc: -------------------------------------------------------------------------------- 1 | #include "main.h" 2 | 3 | using namespace operation_msg; 4 | 5 | int main(void) 6 | { 7 | int bytes_read; 8 | byte buffer[MAX_BUFFER_SIZE]; 9 | 10 | newrelic_register_message_handler(newrelic_message_handler); 11 | 12 | while((bytes_read = read_msg(buffer)) > 0) 13 | { 14 | Operation msg; 15 | Response res; 16 | int ret_code = 0; 17 | msg.ParseFromArray(buffer, bytes_read); 18 | 19 | res.set_error(false); 20 | switch (msg.type()) { 21 | case Operation::INIT: { 22 | Operation_Init op = msg.init(); 23 | ret_code = newrelic_init(op.license().c_str(), 24 | op.app_name().c_str(), 25 | op.language().c_str(), 26 | op.language_version().c_str()); 27 | break; 28 | } 29 | case Operation::ENABLE_INSTRUMENTATION: { 30 | Operation_EnableInstrumentation op = msg.enable_instrumentation(); 31 | newrelic_enable_instrumentation(op.set_enabled()); 32 | ret_code = 0; 33 | break; 34 | } 35 | case Operation::RECORD_METRIC: { 36 | Operation_RecordMetric op = msg.record_metric(); 37 | ret_code = newrelic_record_metric(op.name().c_str(), op.value()); 38 | break; 39 | } 40 | case Operation::RECORD_CPU_USAGE: { 41 | Operation_RecondCPUUsage op = msg.record_cpu_usage(); 42 | ret_code = newrelic_record_cpu_usage(op.cpu_user_time_seconds(), 43 | op.cpu_usage_percent()); 44 | break; 45 | } 46 | case Operation::RECORD_MEMORY_USAGE: { 47 | Operation_RecondMemoryUsage op = msg.record_memory_usage(); 48 | ret_code = newrelic_record_memory_usage(op.memory_megabytes()); 49 | break; 50 | } 51 | case Operation::TRANSACTION_BEGIN: { 52 | Operation_TransactionBegin op = msg.transaction_begin(); 53 | long id = newrelic_transaction_begin(); 54 | if (id < 0) { 55 | ret_code = id; 56 | break; 57 | } 58 | 59 | res.set_transaction_id(id); 60 | ret_code = newrelic_transaction_set_name(id, op.name().c_str()); 61 | if (ret_code < 0) { 62 | break; 63 | } 64 | 65 | if (op.has_set_type_web()) { 66 | ret_code = newrelic_transaction_set_type_web(id); 67 | if (ret_code < 0) { 68 | break; 69 | } 70 | } 71 | 72 | if (op.has_category()) { 73 | ret_code = newrelic_transaction_set_category(id, op.category().c_str()); 74 | if (ret_code < 0) { 75 | break; 76 | } 77 | } 78 | 79 | if (op.has_request_url()) { 80 | ret_code = newrelic_transaction_set_request_url(id, op.request_url().c_str()); 81 | if (ret_code < 0) { 82 | break; 83 | } 84 | } 85 | 86 | if (op.has_max_trace_segments()) { 87 | ret_code = newrelic_transaction_set_max_trace_segments(id, op.max_trace_segments()); 88 | if (ret_code < 0) { 89 | break; 90 | } 91 | } 92 | 93 | break; 94 | } 95 | case Operation::TRANSACTION_END: { 96 | Operation_TransactionEnd op = msg.transaction_end(); 97 | ret_code = newrelic_transaction_end(op.transaction_id()); 98 | break; 99 | } 100 | case Operation::TRANSACTION_NOTICE_ERROR: { 101 | Operation_TransactionNoticeError op = msg.transaction_notice_error(); 102 | ret_code = newrelic_transaction_notice_error(op.transaction_id(), 103 | op.exception_type().c_str(), 104 | op.error_message().c_str(), 105 | op.stack_trace().c_str(), 106 | op.stack_frame_delimiter().c_str()); 107 | break; 108 | } 109 | case Operation::TRANSACTION_ADD_ATTRIBUTE: { 110 | Operation_TransactionAddAttribute op = msg.transaction_add_attribute();; 111 | ret_code = newrelic_transaction_add_attribute(op.transaction_id(), 112 | op.name().c_str(), 113 | op.value().c_str()); 114 | break; 115 | } 116 | case Operation::SEGMENT_GENERIC_BEGIN: { 117 | Operation_SegmentGenericBegin op = msg.segment_generic_begin(); 118 | long id = newrelic_segment_generic_begin(op.transaction_id(), 119 | op.parent_segment_id(), 120 | op.name().c_str()); 121 | if (id < 0) { 122 | ret_code = id; 123 | } 124 | 125 | res.set_segment_id(id); 126 | break; 127 | } 128 | case Operation::SEGMENT_DATASTORE_BEGIN: { 129 | Operation_SegmentDatastoreBegin op = msg.segment_datastore_begin(); 130 | long id = newrelic_segment_datastore_begin(op.transaction_id(), 131 | op.parent_segment_id(), 132 | op.table().c_str(), 133 | get_sql_operation(op.operation()).c_str(), 134 | op.sql().c_str(), 135 | NULL, 136 | NULL); 137 | if (id < 0) { 138 | ret_code = id; 139 | } 140 | 141 | res.set_segment_id(id); 142 | break; 143 | } 144 | case Operation::SEGMENT_EXTERNAL_BEGIN: { 145 | Operation_SegmentExternalBegin op = msg.segment_external_begin(); 146 | long id = newrelic_segment_external_begin(op.transaction_id(), 147 | op.parent_segment_id(), 148 | op.host().c_str(), 149 | op.name().c_str()); 150 | if (id < 0) { 151 | ret_code = id; 152 | } 153 | 154 | res.set_segment_id(id); 155 | break; 156 | } 157 | case Operation::SEGMENT_END: { 158 | Operation_SegmentEnd op = msg.segment_end(); 159 | ret_code = newrelic_segment_end(op.transaction_id(), op.segment_id()); 160 | break; 161 | } 162 | default: 163 | ret_code = -1; 164 | res.set_error(true); 165 | break; 166 | } 167 | 168 | // I don't like this, maybe it should write directly on the stream? 169 | res.set_error((ret_code < 0)); 170 | res.set_code(ret_code); 171 | res.set_error_msg(prase_error(ret_code).c_str()); 172 | int target_size = res.ByteSize(); 173 | byte res_buffer[target_size]; 174 | res.SerializeToArray(res_buffer, target_size); 175 | send_msg(res_buffer, target_size); 176 | } 177 | 178 | return 0; 179 | } 180 | 181 | std::string get_sql_operation(Operation_SegmentDatastoreOperation op) { 182 | switch (op) { 183 | case Operation_SegmentDatastoreOperation_SELECT: { 184 | return NEWRELIC_DATASTORE_SELECT; 185 | } 186 | case Operation_SegmentDatastoreOperation_INSERT: { 187 | return NEWRELIC_DATASTORE_INSERT; 188 | } 189 | case Operation_SegmentDatastoreOperation_UPDATE: { 190 | return NEWRELIC_DATASTORE_UPDATE; 191 | } 192 | case Operation_SegmentDatastoreOperation_DELETE: { 193 | return NEWRELIC_DATASTORE_DELETE; 194 | } 195 | } 196 | } 197 | 198 | std::string prase_error(int error_code) { 199 | switch (error_code) { 200 | case NEWRELIC_RETURN_CODE_OK: return "OK"; 201 | case NEWRELIC_RETURN_CODE_OTHER: return "OTHER"; 202 | case NEWRELIC_RETURN_CODE_DISABLED: return "DISABLED"; 203 | case NEWRELIC_RETURN_CODE_INVALID_PARAM: return "INVALID_PARAM"; 204 | case NEWRELIC_RETURN_CODE_INVALID_ID: return "INVALID_ID"; 205 | case NEWRELIC_RETURN_CODE_TRANSACTION_NOT_STARTED: return "TRANSACTION_NOT_STARTED"; 206 | case NEWRELIC_RETURN_CODE_TRANSACTION_IN_PROGRESS: return "TRANSACTION_IN_PROGRESS"; 207 | case NEWRELIC_RETURN_CODE_TRANSACTION_NOT_NAMED: return "TRANSACTION_NOT_NAMED"; 208 | default: return "UNKNOWN"; 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /c_src/main.h: -------------------------------------------------------------------------------- 1 | #ifndef MAIN_H 2 | #define MAIN_H 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "newrelic_collector_client.h" 11 | #include "newrelic_common.h" 12 | #include "newrelic_transaction.h" 13 | #include "operation_msg.pb.h" 14 | 15 | void newrelic_status_update(int status); 16 | 17 | std::string get_sql_operation(operation_msg::Operation_SegmentDatastoreOperation op); 18 | std::string prase_error(int error_code); 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /c_src/newrelic_collector_client.h: -------------------------------------------------------------------------------- 1 | /** 2 | * This is the C API for the Agent SDK's Collector Client Library. 3 | */ 4 | #ifndef NEWRELIC_COLLECTOR_CLIENT_H_ 5 | #define NEWRELIC_COLLECTOR_CLIENT_H_ 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif /* __cplusplus */ 10 | 11 | /** 12 | * CollectorClient status codes 13 | */ 14 | static const int NEWRELIC_STATUS_CODE_SHUTDOWN = 0; 15 | static const int NEWRELIC_STATUS_CODE_STARTING = 1; 16 | static const int NEWRELIC_STATUS_CODE_STOPPING = 2; 17 | static const int NEWRELIC_STATUS_CODE_STARTED = 3; 18 | 19 | /** 20 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 21 | * Embedded-mode only 22 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 23 | * 24 | * Register this function to handle messages carrying application performance 25 | * data between the instrumented app and embedded CollectorClient. A daemon-mode 26 | * message handler is registered by default. 27 | * 28 | * If you register this handler using newrelic_register_message_handler 29 | * (declared in newrelic_transaction.h), messages will be passed directly 30 | * to the CollectorClient. Otherwise, the daemon-mode message handler will send 31 | * messages to the CollectorClient daemon via domain sockets. 32 | * 33 | * Note: Register newrelic_message_handler before calling newrelic_init. 34 | * 35 | * @param raw_message message containing application performance data 36 | */ 37 | void *newrelic_message_handler(void *raw_message); 38 | 39 | /** 40 | * Register a function to be called whenever the status of the CollectorClient 41 | * changes. 42 | * 43 | * @param callback status callback function to register 44 | */ 45 | void newrelic_register_status_callback(void(*callback)(int)); 46 | 47 | /** 48 | * Start the CollectorClient and the harvester thread that sends application 49 | * performance data to New Relic once a minute. 50 | * 51 | * @param license New Relic account license key 52 | * @param app_name name of instrumented application 53 | * @param language name of application programming language 54 | * @param language_version application programming language version 55 | * @return segment id on success, error code on error, else warning code 56 | */ 57 | int newrelic_init(const char *license, const char *app_name, const char *language, const char *language_version); 58 | 59 | /** 60 | * Tell the CollectorClient to shutdown and stop reporting application 61 | * performance data to New Relic. 62 | * 63 | * @reason reason for shutdown request 64 | * @return 0 on success, error code on error, else warning code 65 | */ 66 | int newrelic_request_shutdown(const char *reason); 67 | 68 | #ifdef __cplusplus 69 | } //! extern "C" 70 | #endif /* __cplusplus */ 71 | 72 | #endif /* NEWRELIC_COLLECTOR_CLIENT_H_ */ 73 | -------------------------------------------------------------------------------- /c_src/newrelic_common.h: -------------------------------------------------------------------------------- 1 | /** 2 | * This is used by TransactionLib and CollectorClientLib 3 | */ 4 | #ifndef NEWRELIC_COMMON_H_ 5 | #define NEWRELIC_COMMON_H_ 6 | 7 | #include 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif /* __cplusplus */ 12 | 13 | /** 14 | * Return codes 15 | */ 16 | static const int NEWRELIC_RETURN_CODE_OK = 0; 17 | static const int NEWRELIC_RETURN_CODE_OTHER = -0x10001; 18 | static const int NEWRELIC_RETURN_CODE_DISABLED = -0x20001; 19 | static const int NEWRELIC_RETURN_CODE_INVALID_PARAM = -0x30001; 20 | static const int NEWRELIC_RETURN_CODE_INVALID_ID = -0x30002; 21 | static const int NEWRELIC_RETURN_CODE_TRANSACTION_NOT_STARTED = -0x40001; 22 | static const int NEWRELIC_RETURN_CODE_TRANSACTION_IN_PROGRESS = -0x40002; 23 | static const int NEWRELIC_RETURN_CODE_TRANSACTION_NOT_NAMED = -0x40003; 24 | 25 | /* 26 | * A basic literal replacement obfuscator that strips the SQL string literals 27 | * (values between single or double quotes) and numeric sequences, replacing 28 | * them with the ? character. 29 | * 30 | * For example: 31 | * 32 | * This SQL: 33 | * SELECT * FROM table WHERE ssn=‘000-00-0000’ 34 | * 35 | * obfuscates to: 36 | * SELECT * FROM table WHERE ssn=? 37 | * 38 | * Because our default obfuscator just replaces literals, there could be 39 | * cases that it does not handle well. For instance, it will not strip out 40 | * comments from your SQL string, it will not handle certain database-specific 41 | * language features, and it could fail for other complex cases. 42 | * 43 | * @param raw a raw sql string 44 | * @return obfuscated sql 45 | */ 46 | char *newrelic_basic_literal_replacement_obfuscator(const char *raw); 47 | 48 | #ifdef __cplusplus 49 | } //! extern "C" 50 | #endif /* __cplusplus */ 51 | 52 | #endif /* NEWRELIC_COMMON_H_ */ 53 | -------------------------------------------------------------------------------- /c_src/newrelic_transaction.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This is the C API for the Agent SDK's Transaction Library 3 | * 4 | * The Transaction Library provides functions that are used to instrument 5 | * application transactions and the segment operations within transactions. 6 | */ 7 | #ifndef NEWRELIC_TRANSACTION_H_ 8 | #define NEWRELIC_TRANSACTION_H_ 9 | 10 | #ifdef __cplusplus 11 | extern "C" { 12 | #endif /* __cplusplus */ 13 | 14 | /* 15 | * NEWRELIC_AUTOSCOPE may be used in place of parent_segment_id to automatically 16 | * identify the last segment that was started within a transaction. 17 | * 18 | * In cases where a transaction runs uninterrupted from beginning to end within 19 | * the same thread, NEWRELIC_AUTOSCOPE may also be used in place of 20 | * transaction_id to automatically identify a transaction. 21 | */ 22 | static const long NEWRELIC_AUTOSCOPE = 1; 23 | 24 | /* 25 | * NEWRELIC_ROOT_SEGMENT is used in place of parent_segment_id when a segment 26 | * does not have a parent. 27 | */ 28 | static const long NEWRELIC_ROOT_SEGMENT = 0; 29 | 30 | /* 31 | * Datastore operations 32 | */ 33 | static const char * const NEWRELIC_DATASTORE_SELECT = "select"; 34 | static const char * const NEWRELIC_DATASTORE_INSERT = "insert"; 35 | static const char * const NEWRELIC_DATASTORE_UPDATE = "update"; 36 | static const char * const NEWRELIC_DATASTORE_DELETE = "delete"; 37 | 38 | /* 39 | * Disable/enable instrumentation. By default, instrumentation is enabled. 40 | * 41 | * All Transaction library functions used for instrumentation will immediately 42 | * return when you disable. 43 | * 44 | * @param set_enabled 0 to disable, 1 to enable 45 | */ 46 | void newrelic_enable_instrumentation(int set_enabled); 47 | 48 | /* 49 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 50 | * Embedded-mode only 51 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 52 | * 53 | * Register a function to handle messages carrying application performance data 54 | * between the instrumented app and CollectorClient. By default, a daemon-mode 55 | * message handler is registered. 56 | * 57 | * If you register the embedded-mode message handler, newrelic_message_handler 58 | * (declared in newrelic_collector_client.h), messages will be passed directly 59 | * to the CollectorClient. Otherwise, the daemon-mode message handler will send 60 | * messages to the CollectorClient via domain sockets. 61 | * 62 | * Note: Register newrelic_message_handler before calling newrelic_init. 63 | * 64 | * @param handler message handler for embedded-mode 65 | */ 66 | void newrelic_register_message_handler(void*(*handler)(void*)); 67 | 68 | /* 69 | * Record a custom metric. 70 | * 71 | * @param name the name of the metric 72 | * @param value the value of the metric 73 | * @return 0 on success, else negative warning code or error code 74 | */ 75 | int newrelic_record_metric(const char *name, double value); 76 | 77 | 78 | /* 79 | * Record CPU user time in seconds and as a percentage of CPU capacity. 80 | * 81 | * @param cpu_user_time_seconds number of seconds CPU spent processing user-level code 82 | * @param cpu_usage_percent CPU user time as a percentage of CPU capacity 83 | * @return 0 on success, else negative warning code or error code 84 | */ 85 | int newrelic_record_cpu_usage(double cpu_user_time_seconds, double cpu_usage_percent); 86 | 87 | /* 88 | * Record the current amount of memory being used. 89 | * 90 | * @param memory_megabytes amount of memory currently being used 91 | * @return 0 on success, else negative warning code or error code 92 | */ 93 | int newrelic_record_memory_usage(double memory_megabytes); 94 | 95 | /* 96 | * Identify the beginning of a transaction. By default, transaction type is set 97 | * to 'WebTransaction' and transaction category is set to 'Uri'. You can change 98 | * the transaction type using newrelic_transaction_set_type_other or 99 | * newrelic_transaction_set_type_web. You can change the transaction category 100 | * using newrelic_transaction_set_category. 101 | * 102 | * @return transaction id on success, else negative warning code or error code 103 | */ 104 | long newrelic_transaction_begin(); 105 | 106 | /* 107 | * Set the transaction type to 'WebTransaction'. This will automatically change 108 | * the category to 'Uri'. You can change the transaction category using 109 | * newrelic_transaction_set_category. 110 | * 111 | * @param transaction_id id of transaction 112 | * @return 0 on success, else negative warning code or error code 113 | */ 114 | int newrelic_transaction_set_type_web(long transaction_id); 115 | 116 | /* 117 | * Set the transaction type to 'OtherTransaction'. This will automatically 118 | * change the category to 'Custom'. You can change the transaction category 119 | * using newrelic_transaction_set_category. 120 | * 121 | * @param transaction_id id of transaction 122 | * @return 0 on success, else negative warning code or error code 123 | */ 124 | int newrelic_transaction_set_type_other(long transaction_id); 125 | 126 | /* 127 | * Set transaction category name, e.g. Uri in WebTransaction/Uri/ 128 | * 129 | * @param transaction_id id of transaction 130 | * @param category name of the transaction category 131 | * @return 0 on success, else negative warning code or error code 132 | */ 133 | int newrelic_transaction_set_category(long transaction_id, const char *category); 134 | 135 | /* 136 | * Identify an error that occurred during the transaction. The first identified 137 | * error is sent with each transaction. 138 | * 139 | * @param transaction_id id of transaction 140 | * @param exception_type type of exception that occurred 141 | * @param error_message error message 142 | * @param stack_trace stacktrace when error occurred 143 | * @param stack_frame_delimiter delimiter to split stack trace into frames 144 | * @return 0 on success, else negative warning code or error code 145 | */ 146 | int newrelic_transaction_notice_error(long transaction_id, const char *exception_type, const char *error_message, const char *stack_trace, const char *stack_frame_delimiter); 147 | 148 | 149 | /* 150 | * Set a transaction attribute. Up to the first 50 attributes added are sent 151 | * with each transaction. 152 | * 153 | * @param transaction_id id of transaction 154 | * @param name attribute name 155 | * @param value attribute value 156 | * @return 0 on success, else negative warning code or error code 157 | */ 158 | int newrelic_transaction_add_attribute(long transaction_id, const char *name, const char *value); 159 | 160 | /* 161 | * Set the name of a transaction. 162 | * 163 | * @param transaction_id id of transaction 164 | * @param name transaction name 165 | * @return 0 on success, else negative warning code or error code 166 | */ 167 | int newrelic_transaction_set_name(long transaction_id, const char *name); 168 | 169 | /* 170 | * Set the request url of a transaction. The query part of the url is 171 | * automatically stripped from the url. 172 | * 173 | * @param transaction_id id of transaction 174 | * @param request_url request url for a web transaction 175 | * @return 0 on success, else negative warning code or error code 176 | */ 177 | int newrelic_transaction_set_request_url(long transaction_id, const char *request_url); 178 | 179 | /* 180 | * Set the maximum number of trace segments allowed in a transaction trace. By 181 | * default, the maximum is set to 2000, which means the first 2000 segments in a 182 | * transaction will create trace segments if the transaction exceeds the 183 | * trace threshold (4 x apdex_t). 184 | * 185 | * @param transaction_id id of transaction 186 | * @param max_trace_segments maximum number of trace segments 187 | * @return 0 on success, else negative warning code or error code 188 | */ 189 | int newrelic_transaction_set_max_trace_segments(long transaction_id, int max_trace_segments); 190 | 191 | /* 192 | * Identify the end of a transaction 193 | * 194 | * @param transaction_id id of transaction 195 | * @return 0 on success, else negative warning code or error code 196 | */ 197 | int newrelic_transaction_end(long transaction_id); 198 | 199 | /* 200 | * Identify the beginning of a segment that performs a generic operation. This 201 | * type of segment does not create metrics, but can show up in a transaction 202 | * trace if a transaction is slow enough. 203 | * 204 | * @param transaction_id id of transaction 205 | * @param parent_segment_id id of parent segment 206 | * @param name name to represent segment 207 | * @return segment id on success, else negative warning code or error code 208 | */ 209 | long newrelic_segment_generic_begin(long transaction_id, long parent_segment_id, const char *name); 210 | 211 | /* 212 | * Identify the beginning of a segment that performs a database operation. 213 | * 214 | * 215 | * SQL Obfuscation 216 | * =============== 217 | * If you supply the sql_obfuscator parameter with NULL, the supplied SQL string 218 | * will go through our basic literal replacement obfuscator that strips the SQL 219 | * string literals (values between single or double quotes) and numeric 220 | * sequences, replacing them with the ? character. For example: 221 | * 222 | * This SQL: 223 | * SELECT * FROM table WHERE ssn=‘000-00-0000’ 224 | * 225 | * obfuscates to: 226 | * SELECT * FROM table WHERE ssn=? 227 | * 228 | * Because our default obfuscator just replaces literals, there could be 229 | * cases that it does not handle well. For instance, it will not strip out 230 | * comments from your SQL string, it will not handle certain database-specific 231 | * language features, and it could fail for other complex cases. 232 | * 233 | * If this level of obfuscation is not sufficient, you can supply your own 234 | * custom obfuscator via the sql_obfuscator parameter. 235 | * 236 | * SQL Trace Rollup 237 | * ================ 238 | * The agent aggregates similar SQL statements together using the supplied 239 | * sql_trace_rollup_name. 240 | * 241 | * To make the most out of this feature, you should either (1) supply the 242 | * sql_trace_rollup_name parameter with a name that describes what the SQL is 243 | * doing, such as "get_user_account" or (2) pass it NULL, in which case 244 | * it will use the sql obfuscator to generate a name. 245 | * 246 | * @param transaction_id id of transaction 247 | * @param parent_segment_id id of parent segment 248 | * @param table name of the database table 249 | * @param operation name of the sql operation 250 | * @param sql the sql string 251 | * @param sql_trace_rollup_name the rollup name for the sql trace 252 | * @param sql_obfuscator a function pointer that takes sql and obfuscates it 253 | * @return segment id on success, else negative warning code or error code 254 | */ 255 | long newrelic_segment_datastore_begin( 256 | long transaction_id, 257 | long parent_segment_id, 258 | const char *table, 259 | const char *operation, 260 | const char *sql, 261 | const char *sql_trace_rollup_name, 262 | char *(*sql_obfuscator)(const char *) 263 | ); 264 | 265 | /* 266 | * Identify the beginning of a segment that performs an external service. 267 | * 268 | * @param transaction_id id of transaction 269 | * @param parent_segment_id id of parent segment 270 | * @param host name of the host of the external call 271 | * @param name name of the external transaction 272 | * @return segment id on success, else negative warning code or error code 273 | */ 274 | long newrelic_segment_external_begin(long transaction_id, long parent_segment_id, const char *host, const char *name); 275 | 276 | /* 277 | * Identify the end of a segment 278 | * 279 | * @param transaction_id id of transaction 280 | * @param egment_id id of the segment to end 281 | * @return 0 on success, else negative warning code or error code 282 | */ 283 | int newrelic_segment_end(long transaction_id, long segment_id); 284 | 285 | #ifdef __cplusplus 286 | } /* ! extern "C" */ 287 | #endif /* __cplusplus */ 288 | 289 | #endif /* NEWRELIC_TRANSACTION_H_ */ 290 | -------------------------------------------------------------------------------- /c_src/operation_msg.proto: -------------------------------------------------------------------------------- 1 | package operation_msg; 2 | 3 | message Response { 4 | required bool error = 1; 5 | required int32 code = 2; 6 | optional string error_msg = 3; 7 | optional uint64 transaction_id = 4; 8 | optional uint64 segment_id = 5; 9 | } 10 | 11 | message Operation { 12 | message Init { 13 | required string license = 1; 14 | required string app_name = 2; 15 | required string language = 3; 16 | required string language_version = 4; 17 | } 18 | 19 | message EnableInstrumentation { 20 | required bool set_enabled = 1; 21 | } 22 | 23 | message RecordMetric { 24 | required string name = 1; 25 | required double value = 2; 26 | } 27 | 28 | message RecondCPUUsage { 29 | required double cpu_user_time_seconds = 1; 30 | required double cpu_usage_percent = 2; 31 | } 32 | 33 | message RecondMemoryUsage { 34 | required double memory_megabytes = 1; 35 | } 36 | 37 | message TransactionBegin { 38 | required string name = 1; 39 | optional bool set_type_web = 2; 40 | optional string category = 3; 41 | optional string request_url = 4; 42 | optional int32 max_trace_segments = 5; 43 | } 44 | 45 | message TransactionEnd { 46 | required uint64 transaction_id = 1; 47 | } 48 | 49 | message TransactionNoticeError { 50 | required uint64 transaction_id = 1; 51 | required string exception_type = 2; 52 | required string error_message = 3; 53 | required string stack_trace = 4; 54 | required string stack_frame_delimiter = 5; 55 | } 56 | 57 | message TransactionAddAttribute { 58 | required uint64 transaction_id = 1; 59 | required string name = 2; 60 | required string value = 3; 61 | } 62 | 63 | message SegmentGenericBegin { 64 | required uint64 transaction_id = 1; 65 | optional uint64 parent_segment_id = 2; 66 | required string name = 3; 67 | } 68 | 69 | message SegmentDatastoreBegin { 70 | required uint64 transaction_id = 1; 71 | optional uint64 parent_segment_id = 2; 72 | required string table = 3; 73 | required SegmentDatastoreOperation operation = 4; 74 | required string sql = 5; 75 | } 76 | 77 | message SegmentExternalBegin { 78 | required uint64 transaction_id = 1; 79 | optional uint64 parent_segment_id = 2; 80 | required string host = 3; 81 | required string name = 4; 82 | } 83 | 84 | message SegmentEnd { 85 | required uint64 transaction_id = 1; 86 | required uint64 segment_id = 2; 87 | } 88 | 89 | enum OperationType { 90 | INIT = 1; 91 | ENABLE_INSTRUMENTATION = 2; 92 | RECORD_METRIC = 3; 93 | RECORD_CPU_USAGE = 4; 94 | RECORD_MEMORY_USAGE = 5; 95 | TRANSACTION_BEGIN = 6; 96 | TRANSACTION_END = 7; 97 | TRANSACTION_NOTICE_ERROR = 8; 98 | TRANSACTION_ADD_ATTRIBUTE = 9; 99 | SEGMENT_GENERIC_BEGIN = 10; 100 | SEGMENT_DATASTORE_BEGIN = 11; 101 | SEGMENT_EXTERNAL_BEGIN = 12; 102 | SEGMENT_END = 13; 103 | } 104 | 105 | enum SegmentDatastoreOperation { 106 | SELECT = 1; 107 | INSERT = 2; 108 | UPDATE = 3; 109 | DELETE = 4; 110 | } 111 | 112 | required OperationType type = 1; 113 | optional Init init = 2; 114 | optional EnableInstrumentation enable_instrumentation = 3; 115 | optional RecordMetric record_metric = 4; 116 | optional RecondCPUUsage record_cpu_usage = 5; 117 | optional RecondMemoryUsage record_memory_usage = 6; 118 | optional TransactionBegin transaction_begin = 7; 119 | optional TransactionEnd transaction_end = 8; 120 | optional TransactionNoticeError transaction_notice_error = 9; 121 | optional TransactionAddAttribute transaction_add_attribute = 10; 122 | optional SegmentGenericBegin segment_generic_begin = 11; 123 | optional SegmentDatastoreBegin segment_datastore_begin = 12; 124 | optional SegmentExternalBegin segment_external_begin = 13; 125 | optional SegmentEnd segment_end = 14; 126 | 127 | } 128 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | -------------------------------------------------------------------------------- /config/log4cplus.properties: -------------------------------------------------------------------------------- 1 | log4cplus.appender.stdout=log4cplus::ConsoleAppender 2 | log4cplus.appender.stdout.layout=log4cplus::PatternLayout 3 | log4cplus.appender.stdout.layout.ConversionPattern=%D [%-5p] %m%n 4 | 5 | log4cplus.appender.CollectorClient=log4cplus::RollingFileAppender 6 | log4cplus.appender.CollectorClient.File=/tmp/newrelic-collector-client.log 7 | log4cplus.appender.CollectorClient.layout=log4cplus::PatternLayout 8 | log4cplus.appender.CollectorClient.layout.ConversionPattern=%D [%-5p] %m%n 9 | log4cplus.appender.CollectorClient.MaxFileSize=20MB 10 | 11 | log4cplus.appender.Transaction=log4cplus::RollingFileAppender 12 | log4cplus.appender.Transaction.File=/tmp/newrelic-transaction.log 13 | log4cplus.appender.Transaction.layout=log4cplus::PatternLayout 14 | log4cplus.appender.Transaction.layout.ConversionPattern=%D [%-5p] %m%n 15 | log4cplus.appender.Transaction.MaxFileSize=60MB 16 | 17 | log4cplus.appender.Common=log4cplus::RollingFileAppender 18 | log4cplus.appender.Common.File=/tmp/newrelic-common.log 19 | log4cplus.appender.Common.layout=log4cplus::PatternLayout 20 | log4cplus.appender.Common.layout.ConversionPattern=%D [%-5p] %m%n 21 | log4cplus.appender.Common.MaxFileSize=60MB 22 | 23 | log4cplus.rootLogger=info, stdout 24 | 25 | log4cplus.logger.com.newrelic=info, stdout 26 | log4cplus.logger.com.newrelic.CollectorClient=INHERITS, stdout, CollectorClient 27 | log4cplus.logger.com.newrelic.CollectorClientLib=INHERITS, stdout, CollectorClient 28 | log4cplus.logger.com.newrelic.CommonLib=INHERITS, stdout, Common 29 | log4cplus.logger.com.newrelic.TransactionLib=INHERITS, stdout, Transaction 30 | 31 | log4cplus.additivity.com.newrelic=false 32 | log4cplus.additivity.com.newrelic.CollectorClient=false 33 | log4cplus.additivity.com.newrelic.CollectorClientLib=false 34 | log4cplus.additivity.com.newrelic.CommonLib=false 35 | log4cplus.additivity.com.newrelic.TransactionLib=false 36 | -------------------------------------------------------------------------------- /lib/newrelic_elixir.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirNewrelic do 2 | 3 | @newrelic_location Application.get_env(:elixir_newrelic, :newrelic_location, "/usr/local/bin/newrelic") 4 | 5 | @spec start_link() :: {:ok, pid} | {:error, term} 6 | def start_link() do 7 | Cure.Server.start_link(@newrelic_location) 8 | end 9 | 10 | @spec stop(pid) :: :ok 11 | def stop(server) do 12 | Cure.Server.stop(server) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/newrelic_elixir/api/agent.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirNewrelic.Api.Agent do 2 | alias ElixirNewrelic.Comm 3 | alias ElixirNewrelic.Messages 4 | 5 | @type agent_reponse :: {:ok, Messages.Response} | {:error, atom} 6 | 7 | @spec init(server :: pid) :: agent_reponse 8 | def init(server) do 9 | license = Application.get_env(:elixir_newrelic, :license) 10 | app_name = Application.get_env(:elixir_newrelic, :app_name) 11 | op = Messages.Operation.Init.new(license: license, 12 | app_name: app_name, 13 | language: "Elixir", 14 | language_version: System.version()) 15 | 16 | msg = Messages.Operation.new(type: :INIT, init: op) 17 | Comm.send_msg(server, msg) 18 | end 19 | 20 | @spec enable_instrumentation(server :: pid, enabled :: boolean) :: agent_reponse 21 | def enable_instrumentation(server, enabled) do 22 | op = Messages.Operation.EnableInstrumentation.new(set_enabled: enabled) 23 | msg = Messages.Operation.new(type: :ENABLE_INSTRUMENTATION, 24 | enable_instrumentation: op) 25 | Comm.send_msg(server, msg) 26 | end 27 | 28 | def record_metric(server, name, value) do 29 | op = Messages.Operation.RecordMetric.new(name: name, value: value) 30 | msg = Messages.Operation.new(type: :RECORD_METRIC, record_metric: op) 31 | Comm.send_msg(server, msg) 32 | end 33 | 34 | def record_cpu_usage(server, cpu_user_time_seconds, cpu_usage_percent) do 35 | op = Messages.Operation.RecondCPUUsage.new( 36 | cpu_user_time_seconds: cpu_user_time_seconds, 37 | cpu_usage_percent: cpu_usage_percent) 38 | msg = Messages.Operation.new(type: :RECORD_CPU_USAGE, record_cpu_usage: op) 39 | Comm.send_msg(server, msg) 40 | end 41 | 42 | def record_memory_usage(server, memory_megabytes) do 43 | op = Messages.Operation.RecondMemoryUsage.new(memory_megabytes: memory_megabytes) 44 | msg = Messages.Operation.new(type: :RECORD_MEMORY_USAGE, record_memory_usage: op) 45 | Comm.send_msg(server, msg) 46 | end 47 | 48 | end 49 | -------------------------------------------------------------------------------- /lib/newrelic_elixir/api/segment.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirNewrelic.Api.Segment do 2 | alias ElixirNewrelic.Comm 3 | alias ElixirNewrelic.Messages 4 | 5 | @type agent_reponse :: {:ok, Messages.Response} | {:error, atom} 6 | 7 | def segment_generic_begin(server, transaction_id, parent_segment_id, name) do 8 | sub = Messages.Operation.SegmentGenericBegin.new( 9 | transaction_id: transaction_id, 10 | parent_segment_id: parent_segment_id, 11 | name: name) 12 | msg = Messages.Operation.new(type: :SEGMENT_GENERIC_BEGIN, 13 | segment_generic_begin: sub) 14 | Comm.send_msg(server, msg) 15 | end 16 | 17 | def segment_datastore_begin(server, transaction_id, parent_segment_id, 18 | table, operation, sql) do 19 | sub = Messages.Operation.SegmentDatastoreBegin.new( 20 | transaction_id: transaction_id, 21 | parent_segment_id: parent_segment_id, 22 | table: table, 23 | operation: operation, 24 | sql: sql) 25 | msg = Messages.Operation.new(type: :SEGMENT_DATASTORE_BEGIN, 26 | segment_datastore_begin: sub) 27 | Comm.send_msg(server, msg) 28 | end 29 | 30 | def segment_external_begin(server, transaction_id, parent_segment_id, host, name) do 31 | sub = Messages.Operation.SegmentExternalBegin.new( 32 | transaction_id: transaction_id, 33 | parent_segment_id: parent_segment_id, 34 | host: host, 35 | name: name) 36 | msg = Messages.Operation.new(type: :SEGMENT_EXTERNAL_BEGIN, 37 | segment_external_begin: sub) 38 | Comm.send_msg(server, msg) 39 | end 40 | 41 | def segment_end(server, transaction_id, segment_id) do 42 | sub = Messages.Operation.SegmentEnd.new( 43 | transaction_id: transaction_id, 44 | segment_id: segment_id) 45 | msg = Messages.Operation.new(type: :SEGMENT_END, segment_end: sub) 46 | Comm.send_msg(server, msg) 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/newrelic_elixir/api/transaction.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirNewrelic.Api.Transaction do 2 | alias ElixirNewrelic.Comm 3 | alias ElixirNewrelic.Messages 4 | 5 | @type agent_reponse :: {:ok, Messages.Response} | {:error, atom} 6 | 7 | def transaction_begin(server, name, set_type_web \\ nil, category \\ nil, 8 | request_url \\ nil, max_trace_segments \\ nil) do 9 | sub = Messages.Operation.TransactionBegin.new( 10 | name: name, 11 | set_type_web: set_type_web, 12 | category: category, 13 | request_url: request_url, 14 | max_trace_segments: max_trace_segments) 15 | msg = Messages.Operation.new(type: :TRANSACTION_BEGIN, transaction_begin: sub) 16 | Comm.send_msg(server, msg) 17 | end 18 | 19 | def transaction_end(server, transaction_id) do 20 | sub = Messages.Operation.TransactionEnd.new(transaction_id: transaction_id) 21 | msg = Messages.Operation.new(type: :TRANSACTION_END, transaction_end: sub) 22 | Comm.send_msg(server, msg) 23 | end 24 | 25 | def transaction_notice_error(server, transaction_id, exception_type, 26 | error_message, stack_trace, stack_frame_delimiter) do 27 | sub = Messages.Operation.TransactionNoticeError.new( 28 | transaction_id: transaction_id, 29 | exception_type: exception_type, 30 | error_message: error_message, 31 | stack_trace: stack_trace, 32 | stack_frame_delimiter: stack_frame_delimiter) 33 | 34 | msg = Messages.Operation.new( 35 | type: :TRANSACTION_NOTICE_ERROR, 36 | transaction_notice_error: sub) 37 | Comm.send_msg(server, msg) 38 | end 39 | 40 | def transaction_add_attribute(server, transaction_id, name, value) do 41 | sub = Messages.Operation.TransactionAddAttribute.new( 42 | transaction_id: transaction_id, 43 | name: name, 44 | value: value,) 45 | msg = Messages.Operation.new(type: :TRANSACTION_ADD_ATTRIBUTE, 46 | transaction_add_attribute: sub) 47 | Comm.send_msg(server, msg) 48 | end 49 | 50 | end 51 | -------------------------------------------------------------------------------- /lib/newrelic_elixir/comm.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirNewrelic.Comm do 2 | alias ElixirNewrelic.Messages 3 | @timeout Application.get_env(:elixir_newrelic, :timeout, 5000) 4 | @type agent_reponse :: {:ok, Messages.Response} | {:error, atom} 5 | 6 | @spec send_msg(server :: pid, msg :: String.t) :: agent_reponse 7 | def send_msg(server, msg) do 8 | Cure.send_data(server, Messages.Operation.encode(msg), :once) 9 | case recv_data do 10 | {:ok, res} -> {:ok, Messages.Response.decode(res)} 11 | {:error, reason} -> {:error, reason} 12 | end 13 | end 14 | 15 | defp recv_data do 16 | receive do 17 | {:cure_data, msg} -> 18 | {:ok, msg} 19 | after @timeout -> 20 | {:error, :timeout} 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/newrelic_elixir/messages.ex: -------------------------------------------------------------------------------- 1 | defmodule ElixirNewrelic.Messages do 2 | use Protobuf, from: Path.expand("../../c_src/operation_msg.proto", __DIR__) 3 | end 4 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirNewrelic.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :elixir_newrelic, 6 | version: "0.2.1", 7 | elixir: "~> 1.1.0", 8 | description: "New Relic Elixir Agent", 9 | deps: deps, 10 | package: package] 11 | end 12 | 13 | def application do 14 | [applications: [:logger, :cure]] 15 | end 16 | 17 | defp deps do 18 | [{:cure, "~> 0.4.1"}, {:exprotobuf, ">=1.0.0-rc1"}] 19 | end 20 | 21 | defp package do 22 | [files: ~w(lib c_src mix.exs README* readme* LICENSE* license*), 23 | maintainers: ["Joey Feldberg"], 24 | licenses: ["MIT"], 25 | links: %{"GitHub" => "https://github.com/joeyfeldberg/elixir-newrelic"}] 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{"cure": {:hex, :cure, "0.4.1"}, 2 | "exprotobuf": {:hex, :exprotobuf, "1.0.0-rc1"}, 3 | "gpb": {:hex, :gpb, "3.18.10"}} 4 | -------------------------------------------------------------------------------- /test/newrelic_elixir_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirNewrelicTest do 2 | use ExUnit.Case 3 | doctest ElixirNewrelic 4 | end 5 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | --------------------------------------------------------------------------------