mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-07 22:09:38 +07:00
[platform] extracting LogUploader from LogPacker
(cherry picked from commit 21f595f5b5aed2a8a41738de3181b82ad79027ed) IJ-CR-159540 GitOrigin-RevId: cb419858a0385aa81c48af746605e573d3c33201
This commit is contained in:
committed by
intellij-monorepo-bot
parent
721af91e43
commit
fb8ee055e4
@@ -32,6 +32,7 @@
|
||||
<orderEntry type="module" module-name="intellij.platform.core.ui" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ide.util.io.impl" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ide.util.netty" />
|
||||
<orderEntry type="module" module-name="intellij.platform.util.progress" />
|
||||
<orderEntry type="library" name="kotlinx-coroutines-core" level="project" />
|
||||
<orderEntry type="library" name="netty-codec-compression" level="project" />
|
||||
</component>
|
||||
|
||||
@@ -6,11 +6,14 @@ import com.intellij.ide.IdeBundle
|
||||
import com.intellij.ide.actions.COLLECT_LOGS_NOTIFICATION_GROUP
|
||||
import com.intellij.ide.actions.ReportFeedbackService
|
||||
import com.intellij.ide.logsUploader.LogPacker
|
||||
import com.intellij.ide.logsUploader.LogUploader
|
||||
import com.intellij.notification.Notification
|
||||
import com.intellij.notification.NotificationType
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.progress.checkCanceled
|
||||
import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream
|
||||
import com.intellij.platform.ide.progress.withBackgroundProgress
|
||||
import com.intellij.platform.util.progress.reportProgress
|
||||
import com.intellij.util.io.jackson.obj
|
||||
import com.intellij.util.ui.IoErrorText
|
||||
import io.netty.channel.ChannelHandlerContext
|
||||
@@ -56,18 +59,26 @@ private class UploadLogsService : RestService() {
|
||||
service<ReportFeedbackService>().coroutineScope.launch {
|
||||
try {
|
||||
withBackgroundProgress(project, IdeBundle.message("collect.upload.logs.progress.title"), true) {
|
||||
try {
|
||||
val byteOut = BufferExposingByteArrayOutputStream()
|
||||
val uploadedID = LogPacker.uploadLogs(project)
|
||||
JsonFactory().createGenerator(byteOut).useDefaultPrettyPrinter().use { writer ->
|
||||
writer.obj {
|
||||
writer.writeStringField("Upload_id", uploadedID)
|
||||
reportProgress { reporter ->
|
||||
reporter.indeterminateStep {
|
||||
@Suppress("IncorrectCancellationExceptionHandling")
|
||||
try {
|
||||
val file = LogPacker.packLogs(project)
|
||||
checkCanceled()
|
||||
val uploadedID = LogUploader.uploadFile(file)
|
||||
LogUploader.notify(project, uploadedID)
|
||||
val byteOut = BufferExposingByteArrayOutputStream()
|
||||
JsonFactory().createGenerator(byteOut).useDefaultPrettyPrinter().use { writer ->
|
||||
writer.obj {
|
||||
writer.writeStringField("Upload_id", uploadedID)
|
||||
}
|
||||
}
|
||||
send(byteOut, request, context)
|
||||
}
|
||||
catch (_: CancellationException) {
|
||||
sendStatus(HttpResponseStatus.BAD_REQUEST, false, channel)
|
||||
}
|
||||
}
|
||||
send(byteOut, request, context)
|
||||
}
|
||||
catch (_: CancellationException) {
|
||||
sendStatus(HttpResponseStatus.BAD_REQUEST, false, channel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,30 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.ide.actions
|
||||
|
||||
import com.intellij.ide.IdeBundle
|
||||
import com.intellij.ide.logsUploader.LogPacker
|
||||
import com.intellij.ide.logsUploader.LogUploader
|
||||
import com.intellij.openapi.progress.checkCanceled
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.platform.ide.progress.withBackgroundProgress
|
||||
import com.intellij.platform.util.progress.reportProgress
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
@ApiStatus.Internal
|
||||
class DefaultReportFeedbackService(override val coroutineScope: CoroutineScope): ReportFeedbackService {
|
||||
|
||||
override suspend fun collectLogs(project: Project?): String? {
|
||||
if (project == null) return null
|
||||
return withBackgroundProgress(project, IdeBundle.message("reportProblemAction.progress.title.submitting"), true) {
|
||||
LogPacker.getBrowseUrl(LogPacker.uploadLogs(project))
|
||||
override suspend fun collectLogs(project: Project?): String? =
|
||||
if (project == null) null
|
||||
else withBackgroundProgress(project, IdeBundle.message("reportProblemAction.progress.title.submitting"), true) {
|
||||
val id = reportProgress { reporter ->
|
||||
reporter.indeterminateStep("") {
|
||||
val file = LogPacker.packLogs(project)
|
||||
checkCanceled()
|
||||
val id = LogUploader.uploadFile(file)
|
||||
LogUploader.notify(project, id)
|
||||
id
|
||||
}
|
||||
}
|
||||
LogUploader.getBrowseUrl(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,60 +1,37 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.ide.logsUploader
|
||||
|
||||
import com.fasterxml.jackson.core.JsonFactory
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.intellij.diagnostic.MacOSDiagnosticReportDirectories
|
||||
import com.intellij.diagnostic.PerformanceWatcher
|
||||
import com.intellij.ide.IdeBundle
|
||||
import com.intellij.ide.actions.COLLECT_LOGS_NOTIFICATION_GROUP
|
||||
import com.intellij.ide.troubleshooting.CompositeGeneralTroubleInfoCollector
|
||||
import com.intellij.ide.troubleshooting.collectDimensionServiceDiagnosticsData
|
||||
import com.intellij.idea.LoggerFactory
|
||||
import com.intellij.notification.Notification
|
||||
import com.intellij.notification.NotificationType
|
||||
import com.intellij.openapi.application.ApplicationNamesInfo
|
||||
import com.intellij.openapi.application.PathManager
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.progress.checkCanceled
|
||||
import com.intellij.openapi.progress.coroutineToIndicator
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.SystemInfoRt
|
||||
import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream
|
||||
import com.intellij.platform.util.progress.reportProgress
|
||||
import com.intellij.troubleshooting.TroubleInfoCollector
|
||||
import com.intellij.util.SystemProperties
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import com.intellij.util.io.Compressor
|
||||
import com.intellij.util.io.HttpRequests
|
||||
import com.intellij.util.io.jackson.obj
|
||||
import com.intellij.util.net.NetUtils
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.io.IOException
|
||||
import java.net.HttpURLConnection
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.io.path.*
|
||||
import kotlin.io.path.exists
|
||||
import kotlin.io.path.forEachDirectoryEntry
|
||||
import kotlin.io.path.isDirectory
|
||||
import kotlin.io.path.name
|
||||
|
||||
@ApiStatus.Internal
|
||||
object LogPacker {
|
||||
private const val UPLOADS_SERVICE_URL = "https://uploads.jetbrains.com"
|
||||
|
||||
private val gson: Gson by lazy {
|
||||
GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
.disableHtmlEscaping()
|
||||
.create()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@RequiresBackgroundThread
|
||||
@Throws(IOException::class)
|
||||
@@ -135,73 +112,6 @@ object LogPacker {
|
||||
archive
|
||||
}
|
||||
|
||||
@RequiresBackgroundThread
|
||||
@Throws(IOException::class)
|
||||
suspend fun uploadLogs(project: Project?): String = reportProgress { reporter ->
|
||||
reporter.indeterminateStep("") {
|
||||
val file = packLogs(project)
|
||||
checkCanceled()
|
||||
|
||||
val folderName = uploadFile(file)
|
||||
|
||||
val message = IdeBundle.message("collect.logs.notification.sent.success", UPLOADS_SERVICE_URL, folderName)
|
||||
Notification(COLLECT_LOGS_NOTIFICATION_GROUP, message, NotificationType.INFORMATION).notify(project)
|
||||
folderName
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun uploadFile(file: Path): String {
|
||||
val responseJson = requestSign(file.name)
|
||||
val uploadUrl = responseJson["url"] as String
|
||||
val folderName = responseJson["folderName"] as String
|
||||
val headers = responseJson["headers"] as Map<*, *>
|
||||
checkCanceled()
|
||||
coroutineToIndicator {
|
||||
upload(file, uploadUrl, headers)
|
||||
}
|
||||
return folderName
|
||||
}
|
||||
|
||||
private fun requestSign(fileName: String): Map<String, Any> {
|
||||
return HttpRequests.post("$UPLOADS_SERVICE_URL/sign", HttpRequests.JSON_CONTENT_TYPE)
|
||||
.accept(HttpRequests.JSON_CONTENT_TYPE)
|
||||
.connect { request ->
|
||||
val out = BufferExposingByteArrayOutputStream()
|
||||
JsonFactory().createGenerator(out).useDefaultPrettyPrinter().use { writer ->
|
||||
writer.obj {
|
||||
writer.writeStringField("filename", fileName)
|
||||
writer.writeStringField("method", "put")
|
||||
writer.writeStringField("contentType", "application/octet-stream")
|
||||
}
|
||||
}
|
||||
request.write(out.toByteArray())
|
||||
gson.fromJson(request.reader, object : TypeToken<Map<String, Any?>?>() {}.type)
|
||||
}
|
||||
}
|
||||
|
||||
private fun upload(file: Path, uploadUrl: String, headers: Map<*, *>) {
|
||||
val indicator = ProgressManager.getGlobalProgressIndicator()
|
||||
HttpRequests.put(uploadUrl, "application/octet-stream")
|
||||
.productNameAsUserAgent()
|
||||
.tuner { urlConnection ->
|
||||
headers.forEach {
|
||||
urlConnection.addRequestProperty(it.key as String, it.value as String)
|
||||
}
|
||||
}
|
||||
.connect {
|
||||
val http = it.connection as HttpURLConnection
|
||||
val length = file.fileSize()
|
||||
http.setFixedLengthStreamingMode(length)
|
||||
http.outputStream.use { outputStream ->
|
||||
file.inputStream().buffered(64 * 1024).use { inputStream ->
|
||||
NetUtils.copyStreamContent(indicator, inputStream, outputStream, length)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getBrowseUrl(folderName: String): String = "$UPLOADS_SERVICE_URL/browse#$folderName"
|
||||
|
||||
private fun doesMacOSDiagnosticReportBelongToThisApp(path: Path): Boolean {
|
||||
val name = path.name
|
||||
if (name.contains(ApplicationNamesInfo.getInstance().scriptName, ignoreCase = true)) return true
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.ide.logsUploader;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.intellij.ide.IdeBundle;
|
||||
import com.intellij.notification.Notification;
|
||||
import com.intellij.notification.NotificationType;
|
||||
import com.intellij.openapi.progress.ProgressIndicatorProvider;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.util.io.HttpRequests;
|
||||
import com.intellij.util.net.NetUtils;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.intellij.ide.actions.CollectZippedLogsActionKt.COLLECT_LOGS_NOTIFICATION_GROUP;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public final class LogUploader {
|
||||
private static final String SERVICE_URL = "https://uploads.jetbrains.com";
|
||||
private static final String BYTES_CONTENT_TYPE = "application/octet-stream";
|
||||
private static final String JSON_CONTENT_TYPE = "application/json";
|
||||
|
||||
public static @NotNull String uploadFile(@NotNull Path file) throws IOException {
|
||||
return uploadFile(file, file.getFileName().toString());
|
||||
}
|
||||
|
||||
public static @NotNull String uploadFile(@NotNull Path file, @NotNull String fileName) throws IOException {
|
||||
var mapper = new ObjectMapper();
|
||||
|
||||
var requestObj = mapper.writeValueAsString(Map.of(
|
||||
"filename", fileName,
|
||||
"method", "put",
|
||||
"contentType", BYTES_CONTENT_TYPE
|
||||
));
|
||||
var responseObj = HttpRequests.post(SERVICE_URL + "/sign", JSON_CONTENT_TYPE + "; charset=utf-8")
|
||||
.accept(JSON_CONTENT_TYPE)
|
||||
.connect(request -> {
|
||||
request.write(requestObj);
|
||||
return mapper.readValue(request.getReader(), Map.class);
|
||||
});
|
||||
|
||||
var uploadUrl = responseObj.get("url").toString();
|
||||
@SuppressWarnings("unchecked")
|
||||
var headers = (Map<String, String>)responseObj.get("headers");
|
||||
var id = responseObj.get("folderName").toString();
|
||||
|
||||
@SuppressWarnings("UsagesOfObsoleteApi")
|
||||
var indicator = ProgressIndicatorProvider.getGlobalProgressIndicator();
|
||||
HttpRequests.put(uploadUrl, "application/octet-stream")
|
||||
.productNameAsUserAgent()
|
||||
.tuner(urlConnection -> headers.forEach((k, v) -> urlConnection.addRequestProperty(k, v)))
|
||||
.connect(it -> {
|
||||
var http = ((HttpURLConnection)it.getConnection());
|
||||
var length = Files.size(file);
|
||||
http.setFixedLengthStreamingMode(length);
|
||||
try (var outputStream = http.getOutputStream(); var inputStream = new BufferedInputStream(http.getInputStream(), 64 * 1024)) {
|
||||
NetUtils.copyStreamContent(indicator, inputStream, outputStream, length);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
public static void notify(@Nullable Project project, @NotNull String id) {
|
||||
var message = IdeBundle.message("collect.logs.notification.sent.success", SERVICE_URL, id);
|
||||
new Notification(COLLECT_LOGS_NOTIFICATION_GROUP, message, NotificationType.INFORMATION)
|
||||
.notify(project);
|
||||
}
|
||||
|
||||
public static @NotNull String getBrowseUrl(@NotNull String id) {
|
||||
return SERVICE_URL + "/browse#" + id;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user