#node.js #json #fs
Вопрос:
Я пытаюсь реализовать журналы JSON для своего Node.js сервер; однако, когда я быстро отправляю запросы, JSON.parse()
выдает ошибки. Я полагаю, что это может быть вызвано одновременным чтением и записью в мой файл журнала, поскольку fs
методы являются асинхронными.
Одна из ошибок, которые я получил, была:
SyntaxError: Unexpected end of JSON input
Это можно было бы исправить, снизив скорость запросов.
Однако в других случаях сам JSON получал синтаксические ошибки, и журналы не могли быть проанализированы, пока я не удалил их и не перезапустил свой сервер:
SyntaxError: Unexpected token [TOKEN] in JSON at position [POSITION]
Иногда конец бревен выглядел так, заканчиваясь дополнительным ]
:
[
...,
{
"ip": ...,
"url": ...,
"ua": ...
}
]]
Или это:
[
...,
{
"ip": ...,
"url": ...,
"ua": ...
}
]
]
Это очень упрощенная версия моего сервера:
"use strict"
const fsp = require("fs").promises
const http = require("http")
const appendJson = async (loc, content) => {
const data = JSON.parse(
await fsp.readFile(loc, "utf-8").catch(err => "[]")
)
data.push(content)
fsp.writeFile(loc, JSON.stringify(data))
}
const logReq = async (req, res) => {
appendJson(__dirname "/log.json", {
ip: req.socket.remoteAddress,
url: req.method " http://" req.headers.host req.url,
ua: "User-Agent: " req.headers["user-agent"],
})
}
const html = `<head><link rel="stylesheet" href="/main.css"></head><script src="/main.js"></script>`
const respond = async (req, res) => {
res.writeHead(200, { "Content-Type": "text/html" }).end(html)
logReq(req, res)
}
http.createServer(respond).listen(8080)
Я протестировал отправку большого количества запросов как в Firefox, так и в Chromium (но по какой-то причине при отправке даже тысяч запросов с помощью cURL не возникало ошибок), быстро обновив страницу или открыв множество вкладок в консоли браузера:
for (let i = 0; i < 200; i )
window.open("http://localhost:8080")
Как правило, с полными HTML-страницами, которые сами по себе делали больше запросов, гораздо меньшее количество запросов вызывало бы эти ошибки.
В чем причина этих ошибок и как я могу их исправить, особенно вторую?
Комментарии:
1. Я верю, что вы могли бы просто
await fsp.writeFile(...)
. Вы этого не ждете, у меня есть интуиция, что это может решить проблему
Ответ №1:
Одновременные запросы к вашему appendJson()
методу являются причиной вашей проблемы. Пока выполняется один веб-запрос, поступает другой. Вы должны организовать доступ к файлу журнала таким образом, чтобы в любое время выполнялся только один одновременный доступ.
Что-то подобное может сработать, если у вас есть только один файл журнала.
Там есть fileAccessInProgress
флаг и очередь элементов для записи в файл. Каждый новый элемент добавляется в очередь. Затем, если доступ к файлам не активен, содержимое очереди записывается. Если новые элементы поступают во время доступа, они также добавляются в очередь.
let fileAccessInProgress = false
let logDataQueue = []
const appendJson = async (loc, content) => {
logDataQueue.push(content)
if (fileAccessInProgress) return
fileAccessInProgress = true
while (logDataQueue.length > 0) {
const data = JSON.parse(
await fsp.readFile(loc, "utf-8").catch(err => "[]")
)
while (logDataQueue.length > 0) data.push(logDataQueue.shift())
await fsp.writeFile(loc, JSON.stringify(data))
}
fileAccessInProgress = false
}
Вероятно, вы сможете заставить это работать. Но, при всем уважении, это плохой способ ведения журнала.Почему? Нагрузка на процессор и ввод-вывод при записи каждого элемента файла журнала пропорциональна количеству элементов, уже содержащихся в файле журнала. На жаргоне compsci big-O это означает, что запись файла loc равна O(n в квадрате).
Это означает, что чем успешнее становится ваше приложение, тем медленнее оно будет работать.
Есть причина, по которой файлы журналов содержат отдельные строки журнала, а не полные объекты JSON: чтобы избежать такого снижения производительности. Если вам нужен объект JSON для обработки строк журнала, создайте его при чтении журнала, а не при его записи.
Комментарии:
1. без вашего последнего абзаца: Один из способов избежать необходимости читать и записывать весь файл каждый раз, когда строка регистрируется , — это добавить одну строку JSON новую строку . Существуют библиотеки для чтения файлов JSON с разделителями новой строки (это очень простой формат).