Open samssann opened 4 years ago
Thanks definitely possible and probably not hard to do, but it might be a while till I get around to implementing new appenders... A pull request would be appreciated though :)
Roger! I think I won't have time until maybe the start of next year for a full PR, but I did some preliminary work. AzureAuth can't be used since the workspace_id and workspace_key are not associated with Azure AD, but the Log Analytics workspace itself. The dependency requirements grew a bit, but these can be bypassed with more code.
#' @importFrom rlang is_scalar_character abort
#' @importFrom xml2 as_list
#' @importFrom httr POST add_headers content_type_json content
#' @importFrom glue glue
#' @importFrom tibble is_tibble
#' @importFrom digest hmac
#' @importFrom lubridate tz
#' @importFrom jsonlite toJSON base64_enc base64_dec
azure_write_logs <- function(tbl, log_type, workspace_id, workspace_key) {
stopifnot(
is_tibble(tbl),
is_scalar_character(log_type),
is_scalar_character(workspace_id),
is_scalar_character(workspace_key)
)
url <- as.character(glue("https://{workspace_id}.ods.opinsights.azure.com/api/logs?api-version=2016-04-01"))
content <- as.character(toJSON(tbl, auto_unbox = T))
content_len <- nchar(content)
content_size <- as.numeric(gsub(" bytes", "", object.size(content)))
if(content_size > 30000000L) abort("<api_error> tbl cannot exceed size of 30 MB")
datetime <- Sys.time()
tz(datetime) <- "GMT"
datetime <- format(datetime, "%a, %d %b %Y %X %Z")
message <- as.character(glue('POST\n{content_len}\napplication/json\nx-ms-date:{datetime}\n/api/logs'))
decoded_key <- rawToChar(base64_dec(workspace_key))
encoded_hash <- base64_enc(rawToChar(hmac(raw = T, key = decoded_key, object = message, algo = "sha256")))
signature <- as.character(glue("SharedKey {workspace_id}:{encoded_hash}"))
response <- POST(
url,
content_type_json(),
add_headers(
`Authorization` = signature,
`Log-Type` = log_type,
`x-ms-date` = datetime
),
body = content
)
if(response$status_code != 200L) abort(glue("<api_error> {paste0(xml2::as_list(content(response))$html$body, collapse = '')}")) # abort if request did not go through
invisible(TRUE)
}
ok thanks that is helpful. my main goal is right now to get lgrExtra ready for cran till January. I'll look into this issue after that
Best of luck. Many thanks for this and the lgr
package. I'll let you know if I start working this issue beforehand.
Have you had a chance to look the implementation on this? It seems pretty straight forward if we create a new appender (AppenderAzureLog) than initializes like this
aal <- AppenderAzureLog$new(
workspace_id = "xxx",
workspace_key = "xxx",
log_type = "test"
)
This appender should inherit the AppenderJSON (as json format is used with the http requests body)?
I did a mini proof-of-concept and it worked
# this should be a private method in the AppenderAzureLog class
upload_logs <- function(json, log_type, workspace_id, workspace_key) {
stopifnot(
inherits(json, "json"),
is_scalar_character(log_type),
is_scalar_character(workspace_id),
is_scalar_character(workspace_key)
)
url <- as.character(glue("https://{workspace_id}.ods.opinsights.azure.com/api/logs?api-version=2016-04-01"))
content <- as.character(json)
content_len <- nchar(content)
content_size <- as.numeric(gsub(" bytes", "", object.size(content)))
if(content_size > 30000000L) abort("<api_error> json cannot exceed size of 30 MB") # any ideas on how to present errors so that it matches your general idea of the package?
datetime <- Sys.time()
tz(datetime) <- "GMT"
datetime <- format(datetime, "%a, %d %b %Y %X %Z")
message <- as.character(glue('POST\n{content_len}\napplication/json\nx-ms-date:{datetime}\n/api/logs'))
decoded_key <- rawToChar(base64_dec(workspace_key))
encoded_hash <- base64_enc(rawToChar(hmac(raw = T, key = decoded_key, object = message, algo = "sha256")))
signature <- as.character(glue("SharedKey {workspace_id}:{encoded_hash}"))
response <- POST(
url,
content_type_json(),
add_headers(
`Authorization` = signature,
`Log-Type` = log_type,
`x-ms-date` = datetime,
`time-generated-field` = "timestamp" # points to the timestamp field is json
),
body = content
)
if(response$status_code != 200L) abort(glue("<api_error> {paste0(xml2::as_list(content(response))$html$body, collapse = '')}")) # abort if request did not go through
invisible(TRUE)
}
# create event
event <- LogEvent$new(
logger = Logger$new("dummy logger"),
level = 200,
timestamp = Sys.time(),
caller = NA_character_,
msg = "a test message",
custom_field = "LayoutJson can handle arbitrary fields"
)
lo <- LayoutJson$new() # json layout
lo$set_timestamp_fmt("%Y-%m-%dT%H:%M:%SZ") # the data ingestion supports only the ISO 8601 format
json <- lo$format_event(event)
json
#> {"level":200,"timestamp":"2021-04-01T11:33:42Z","logger":"dummy logger","caller":null,"msg":"a test message","custom_field":"LayoutJson can handle arbitrary fields"}
upload_logs(json, "test", .id, .key) # upload log event
After waiting for a couple of minutes, this appeared in the Azure Log Analytics portal
A "_CL" suffix is added automatically to the log_type
variable in the ingestion phase to point out that this indeed a custom log. The only "hard" thing is to handle errors in the http request and only delete events from the buffer if the upload is successful. But I think the framework you've done already pretty much enables this. The API has not changed in years so keeping this up-to-date seems easy.
Probably there should be a new AppenderHttp
or AppenderRest
that uses LayoutJson
, as Appenders are responsible for the destination, and Layouts for the format. AppenderJson
itself is kinda bad design from that standpoint, but it's really just a convenient shortcut for AppenderFile
with LayoutJson
.
I'm kinda busy with other things at the moment, so I'm not sure if I get around to it... If you have some experience with R6 classes you could probably hack together your own appender for the time beeing...
Anyways If I might get around to it, I would need to be able to setup a test destination. Is that easy/free with azure?
Understood. Its pretty easy to setup (and free https://azure.microsoft.com/en-us/services/monitor/). I'll manage to create a custom appender for myself, but I'll probably leave the AppenderHttp/Rest design for you when you have the time. I can share my fork when I get to building it.
Ok cool thanks :) I'll hope I'll have some time to work more on lgr later this year but at the moment I'm pretty swamped at work.
Log appender that sends logs to Azure Log Analytics. Azure Log analytics API reference https://docs.microsoft.com/en-us/rest/api/loganalytics/create-request
Azure Log Analytics would allow sending application logs to a centralized location, where they can be analysed using Kusto Query Language (KQL) and set to trigger automated alerts based on these queries.
Edit: Added use case.