diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp
index 9799ed19383117333ded67f6a76381b994d181d7..b4c6a647d70d6626ada1e01c2f39b89a50af2d8b 100644
--- a/indra/llcommon/llprocess.cpp
+++ b/indra/llcommon/llprocess.cpp
@@ -50,6 +50,7 @@
 static const char* whichfile[] = { "stdin", "stdout", "stderr" };
 static std::string empty;
 static LLProcess::Status interpret_status(int status);
+static std::string getDesc(const LLProcess::Params& params);
 
 /**
  * Ref-counted "mainloop" listener. As long as there are still outstanding
@@ -404,7 +405,7 @@ LLProcessPtr LLProcess::create(const LLSDOrParams& params)
 			LLEventPumps::instance().obtain(params.postend)
 				.post(LLSDMap
 					  // no "id"
-					  ("desc", std::string(params.executable))
+					  ("desc", getDesc(params))
 					  ("state", LLProcess::UNSTARTED)
 					  // no "data"
 					  ("string", e.what())
@@ -561,8 +562,8 @@ LLProcess::LLProcess(const LLSDOrParams& params):
 	sProcessListener.addPoll(*this);
 	mStatus.mState = RUNNING;
 
-	mDesc = STRINGIZE(LLStringUtil::quote(params.executable) << " (" << mProcess.pid << ')');
-	LL_INFOS("LLProcess") << "Launched " << params << " (" << mProcess.pid << ")" << LL_ENDL;
+	mDesc = STRINGIZE(getDesc(params) << " (" << mProcess.pid << ')');
+	LL_INFOS("LLProcess") << mDesc << ": launched " << params << LL_ENDL;
 
 	// Unless caller explicitly turned off autokill (child should persist),
 	// take steps to terminate the child. This is all suspenders-and-belt: in
@@ -604,6 +605,29 @@ LLProcess::LLProcess(const LLSDOrParams& params):
 	}
 }
 
+// Helper to obtain a description string, given a Params block
+static std::string getDesc(const LLProcess::Params& params)
+{
+	// If caller specified a description string, by all means use it.
+	std::string desc(params.desc);
+	if (! desc.empty())
+		return desc;
+
+	// Caller didn't say. Use the executable name -- but use just the filename
+	// part. On Mac, for instance, full pathnames get cumbersome.
+	// If there are Linden utility functions to manipulate pathnames, I
+	// haven't found them -- and for this usage, Boost.Filesystem seems kind
+	// of heavyweight.
+	std::string executable(params.executable);
+	std::string::size_type delim = executable.find_last_of("\\/");
+	// If executable contains no pathname delimiters, return the whole thing.
+	if (delim == std::string::npos)
+		return executable;
+
+	// Return just the part beyond the last delimiter.
+	return executable.substr(delim + 1);
+}
+
 LLProcess::~LLProcess()
 {
 	// Only in state RUNNING are we registered for callback. In UNSTARTED we
diff --git a/indra/llcommon/llprocess.h b/indra/llcommon/llprocess.h
index 96a3dce5b30ca6998ce8850bee1d72e7f870f059..d005847e18ea95f307f130d829387a33ca61c72a 100644
--- a/indra/llcommon/llprocess.h
+++ b/indra/llcommon/llprocess.h
@@ -159,7 +159,8 @@ class LL_COMMON_API LLProcess: public boost::noncopyable
 			cwd("cwd"),
 			autokill("autokill", true),
 			files("files"),
-			postend("postend")
+			postend("postend"),
+			desc("desc")
 		{}
 
 		/// pathname of executable
@@ -199,6 +200,13 @@ class LL_COMMON_API LLProcess: public boost::noncopyable
 		 *   with code 0")
 		 */
 		Optional<std::string> postend;
+		/**
+		 * Description of child process for logging purposes. It need not be
+		 * unique; the logged description string will contain the PID as well.
+		 * If this is omitted, a description will be derived from the
+		 * executable name.
+		 */
+		Optional<std::string> desc;
 	};
 	typedef LLSDParamAdapter<Params> LLSDOrParams;
 
diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp
index 1a755c283c4bbb1a2f66f7626642427296b15924..fe599e78921f6ac8381e4d77cedbaf43befcbbd6 100644
--- a/indra/llcommon/tests/llprocess_test.cpp
+++ b/indra/llcommon/tests/llprocess_test.cpp
@@ -136,6 +136,7 @@ struct PythonProcessLauncher
         const char* PYTHON(getenv("PYTHON"));
         tut::ensure("Set $PYTHON to the Python interpreter", PYTHON);
 
+        mParams.desc = desc + " script";
         mParams.executable = PYTHON;
         mParams.args.add(mScript.getName());
     }
@@ -1244,17 +1245,14 @@ namespace tut
         std::string pumpname("postend");
         EventListener listener(LLEventPumps::instance().obtain(pumpname));
         LLProcess::Params params;
+        params.desc = "bad postend";
         params.postend = pumpname;
         LLProcessPtr child = LLProcess::create(params);
         ensure("shouldn't have launched", ! child);
         ensure_equals("number of postend events", listener.mHistory.size(), 1);
         LLSD postend(listener.mHistory.front());
         ensure("has id", ! postend.has("id"));
-        // Ha ha, in this case the implementation normally sets "desc" to
-        // params.executable. But as the nature of the problem is that
-        // params.executable is empty, expecting "desc" to be nonempty is a
-        // bit unreasonable!
-        //ensure("desc empty", ! postend["desc"].asString().empty());
+        ensure_equals("desc", postend["desc"].asString(), std::string(params.desc));
         ensure_equals("state", postend["state"].asInteger(), LLProcess::UNSTARTED);
         ensure("has data", ! postend.has("data"));
         std::string error(postend["string"]);